stageflow 0.25.0 → 0.27.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 (501) hide show
  1. package/README.md +10 -3
  2. package/dist/a2a/contracts.d.ts +1 -0
  3. package/dist/a2a/contracts.js +1 -0
  4. package/dist/a2a/registry.d.ts +2 -0
  5. package/dist/a2a/registry.js +20 -1
  6. package/dist/a2a/server.d.ts +7 -0
  7. package/dist/a2a/server.js +5 -2
  8. package/dist/a2a/service.d.ts +1 -1
  9. package/dist/a2a/service.js +57 -8
  10. package/dist/a2a/store.d.ts +8 -5
  11. package/dist/a2a/store.js +24 -67
  12. package/dist/agent/activity.d.ts +3 -0
  13. package/dist/agent/bootProviderConfig.d.ts +21 -0
  14. package/dist/agent/bootProviderConfig.js +74 -0
  15. package/dist/agent/piAdapter.d.ts +9 -1
  16. package/dist/agent/piAdapter.js +58 -3
  17. package/dist/agent/piIsolatedMcp.d.ts +2 -0
  18. package/dist/agent/piIsolatedMcp.js +10 -2
  19. package/dist/agent/port.d.ts +6 -0
  20. package/dist/agent/streamLogRedact.d.ts +1 -1
  21. package/dist/agent/streamLogRedact.js +4 -11
  22. package/dist/cli/artifactCommand.js +9 -16
  23. package/dist/cli/backupCommand.d.ts +9 -0
  24. package/dist/cli/backupCommand.js +111 -0
  25. package/dist/cli/debugRunCommand.d.ts +10 -0
  26. package/dist/cli/debugRunCommand.js +109 -0
  27. package/dist/cli/doctorCommand.d.ts +32 -0
  28. package/dist/cli/doctorCommand.js +453 -0
  29. package/dist/cli/envelopeCommand.js +9 -2
  30. package/dist/cli/exportAllCommand.d.ts +55 -0
  31. package/dist/cli/exportAllCommand.js +218 -0
  32. package/dist/cli/exportRunCommand.js +23 -21
  33. package/dist/cli/hostClient.d.ts +21 -3
  34. package/dist/cli/hostClient.js +93 -12
  35. package/dist/cli/initTemplates.d.ts +1 -1
  36. package/dist/cli/initTemplates.js +8 -1
  37. package/dist/cli/resolveSafeOutPath.d.ts +2 -0
  38. package/dist/cli/resolveSafeOutPath.js +40 -0
  39. package/dist/cli/restoreCommand.d.ts +10 -0
  40. package/dist/cli/restoreCommand.js +90 -0
  41. package/dist/cli/runCommand.d.ts +3 -2
  42. package/dist/cli/runCommand.js +93 -11
  43. package/dist/cli/runOutput.d.ts +4 -0
  44. package/dist/cli/runOutput.js +33 -7
  45. package/dist/cli/runStageCommand.d.ts +1 -0
  46. package/dist/cli/runStageCommand.js +44 -25
  47. package/dist/cli/runsCommand.d.ts +1 -1
  48. package/dist/cli/runsCommand.js +162 -8
  49. package/dist/cli.js +171 -30
  50. package/dist/config/catalogRelativePath.d.ts +62 -0
  51. package/dist/config/catalogRelativePath.js +118 -0
  52. package/dist/config/configOrigin.d.ts +27 -0
  53. package/dist/config/configOrigin.js +59 -0
  54. package/dist/config/hostConfig.d.ts +55 -0
  55. package/dist/config/hostConfig.js +333 -0
  56. package/dist/config/listSkills.d.ts +26 -0
  57. package/dist/config/listSkills.js +190 -3
  58. package/dist/config/loadOutcome.d.ts +1 -1
  59. package/dist/config/loadPipeline.js +23 -2
  60. package/dist/config/loadStage.js +18 -0
  61. package/dist/config/loadTask.d.ts +2 -0
  62. package/dist/config/loadTask.js +57 -18
  63. package/dist/config/materializeStageModels.js +5 -1
  64. package/dist/config/mergePipelineIncludes.d.ts +3 -0
  65. package/dist/config/mergePipelineIncludes.js +19 -0
  66. package/dist/config/migrateYaml.js +6 -11
  67. package/dist/config/multiProjectCatalog.d.ts +30 -0
  68. package/dist/config/multiProjectCatalog.js +142 -0
  69. package/dist/config/normalizePipelineStageEntry.js +20 -1
  70. package/dist/config/pipelineStageKeys.js +2 -0
  71. package/dist/config/resolveCatalogRoots.d.ts +27 -0
  72. package/dist/config/resolveCatalogRoots.js +76 -0
  73. package/dist/config/resolveModel.d.ts +7 -1
  74. package/dist/config/resolveModel.js +11 -1
  75. package/dist/config/resolveStageMcpServers.d.ts +9 -1
  76. package/dist/config/resolveStageMcpServers.js +36 -9
  77. package/dist/config/secretFromEnvOrFile.d.ts +9 -0
  78. package/dist/config/secretFromEnvOrFile.js +34 -0
  79. package/dist/config/seededCatalog.d.ts +11 -0
  80. package/dist/config/seededCatalog.js +29 -0
  81. package/dist/config/toolRequires.d.ts +24 -0
  82. package/dist/config/toolRequires.js +115 -0
  83. package/dist/config/validateCatalog.d.ts +5 -1
  84. package/dist/config/validateCatalog.js +140 -1
  85. package/dist/diagnostics/checks.d.ts +39 -0
  86. package/dist/diagnostics/checks.js +135 -0
  87. package/dist/errors/codes.d.ts +14 -0
  88. package/dist/errors/codes.js +59 -0
  89. package/dist/git/cache.d.ts +14 -0
  90. package/dist/git/cache.js +150 -0
  91. package/dist/git/credentials.d.ts +9 -0
  92. package/dist/git/credentials.js +88 -0
  93. package/dist/git/errors.d.ts +20 -0
  94. package/dist/git/errors.js +94 -0
  95. package/dist/git/exec.d.ts +16 -0
  96. package/dist/git/exec.js +135 -0
  97. package/dist/git/operations.d.ts +29 -0
  98. package/dist/git/operations.js +153 -0
  99. package/dist/git/version.d.ts +1 -0
  100. package/dist/git/version.js +10 -0
  101. package/dist/logging/audit.d.ts +16 -0
  102. package/dist/logging/audit.js +19 -0
  103. package/dist/logging/commonWords.d.ts +2 -0
  104. package/dist/logging/commonWords.js +13 -0
  105. package/dist/logging/logger.d.ts +49 -0
  106. package/dist/logging/logger.js +193 -0
  107. package/dist/logging/namedSecrets.d.ts +6 -0
  108. package/dist/logging/namedSecrets.js +58 -0
  109. package/dist/logging/redact.d.ts +13 -0
  110. package/dist/logging/redact.js +88 -0
  111. package/dist/mcp/catalogTools.js +456 -80
  112. package/dist/mcp/checkoutTools.d.ts +52 -0
  113. package/dist/mcp/checkoutTools.js +332 -0
  114. package/dist/mcp/controlTools.js +179 -5
  115. package/dist/mcp/deps.d.ts +1 -0
  116. package/dist/mcp/getStartedTool.d.ts +3 -0
  117. package/dist/mcp/getStartedTool.js +62 -0
  118. package/dist/mcp/readArtifact.d.ts +11 -0
  119. package/dist/mcp/readArtifact.js +46 -30
  120. package/dist/mcp/server.js +1 -0
  121. package/dist/mcp/tools.js +4 -0
  122. package/dist/net/proxy.d.ts +14 -0
  123. package/dist/net/proxy.js +78 -0
  124. package/dist/package-meta.d.ts +2 -1
  125. package/dist/package-meta.js +2 -1
  126. package/dist/preflight/bash.d.ts +8 -0
  127. package/dist/preflight/bash.js +34 -0
  128. package/dist/preflight/claudeRoot.d.ts +11 -0
  129. package/dist/preflight/claudeRoot.js +23 -0
  130. package/dist/preflight/mcpCommands.d.ts +8 -0
  131. package/dist/preflight/mcpCommands.js +36 -0
  132. package/dist/preflight/pipelinePreflight.d.ts +40 -0
  133. package/dist/preflight/pipelinePreflight.js +192 -0
  134. package/dist/preflight/tls.d.ts +6 -0
  135. package/dist/preflight/tls.js +18 -0
  136. package/dist/preflight/toolchain.d.ts +45 -0
  137. package/dist/preflight/toolchain.js +219 -0
  138. package/dist/project/findProjectRoot.js +3 -4
  139. package/dist/project/globalHome.d.ts +7 -0
  140. package/dist/project/globalHome.js +63 -2
  141. package/dist/project/resolveProjectContext.js +2 -2
  142. package/dist/project/resolveStageflowContext.js +2 -2
  143. package/dist/projection/projectRun.d.ts +2 -1
  144. package/dist/projection/projectRun.js +1 -0
  145. package/dist/runstore/assertTmpdir.d.ts +1 -0
  146. package/dist/runstore/assertTmpdir.js +21 -0
  147. package/dist/runstore/backup.d.ts +52 -0
  148. package/dist/runstore/backup.js +292 -0
  149. package/dist/runstore/createStore.d.ts +18 -1
  150. package/dist/runstore/createStore.js +64 -5
  151. package/dist/runstore/debugBundle.d.ts +60 -0
  152. package/dist/runstore/debugBundle.js +143 -0
  153. package/dist/runstore/diskUsage.d.ts +50 -0
  154. package/dist/runstore/diskUsage.js +214 -0
  155. package/dist/runstore/exportRunPayload.d.ts +8 -0
  156. package/dist/runstore/exportRunPayload.js +23 -0
  157. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  158. package/dist/runstore/normalizeCatalogPath.js +10 -0
  159. package/dist/runstore/paths.d.ts +5 -0
  160. package/dist/runstore/paths.js +145 -1
  161. package/dist/runstore/port.d.ts +111 -13
  162. package/dist/runstore/port.js +8 -1
  163. package/dist/runstore/restore.d.ts +81 -0
  164. package/dist/runstore/restore.js +355 -0
  165. package/dist/runstore/retention.d.ts +27 -0
  166. package/dist/runstore/retention.js +74 -0
  167. package/dist/runstore/runManifest.d.ts +146 -0
  168. package/dist/runstore/runManifest.js +288 -0
  169. package/dist/runstore/runProjection.d.ts +3 -1
  170. package/dist/runstore/runProjection.js +63 -1
  171. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -3
  172. package/dist/runstore/sqlite/SqliteRunStore.js +352 -219
  173. package/dist/runstore/sqlite/applyStorePragmas.d.ts +39 -0
  174. package/dist/runstore/sqlite/applyStorePragmas.js +145 -0
  175. package/dist/runstore/sqlite/migrations/001-baseline.d.ts +7 -0
  176. package/dist/runstore/sqlite/migrations/001-baseline.js +10 -0
  177. package/dist/runstore/sqlite/migrations/002-repository-binding.d.ts +7 -0
  178. package/dist/runstore/sqlite/migrations/002-repository-binding.js +29 -0
  179. package/dist/runstore/sqlite/migrations/003-run-lifecycle.d.ts +7 -0
  180. package/dist/runstore/sqlite/migrations/003-run-lifecycle.js +28 -0
  181. package/dist/runstore/sqlite/migrations/004-auto-resume-count.d.ts +7 -0
  182. package/dist/runstore/sqlite/migrations/004-auto-resume-count.js +19 -0
  183. package/dist/runstore/sqlite/migrations/005-config-origins.d.ts +7 -0
  184. package/dist/runstore/sqlite/migrations/005-config-origins.js +19 -0
  185. package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.d.ts +7 -0
  186. package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.js +28 -0
  187. package/dist/runstore/sqlite/migrations/007-projects-registry.d.ts +7 -0
  188. package/dist/runstore/sqlite/migrations/007-projects-registry.js +31 -0
  189. package/dist/runstore/sqlite/migrations/index.d.ts +21 -0
  190. package/dist/runstore/sqlite/migrations/index.js +72 -0
  191. package/dist/runstore/sqlite/schema.d.ts +20 -1
  192. package/dist/runstore/sqlite/schema.js +277 -1
  193. package/dist/runstore/sqlite/storeOpenError.d.ts +6 -0
  194. package/dist/runstore/sqlite/storeOpenError.js +11 -0
  195. package/dist/runstore/sqlite/storeSchemaError.d.ts +5 -0
  196. package/dist/runstore/sqlite/storeSchemaError.js +8 -0
  197. package/dist/runstore/stageExecution.js +3 -0
  198. package/dist/runstore/storeFilesystem.d.ts +32 -0
  199. package/dist/runstore/storeFilesystem.js +148 -0
  200. package/dist/runstore/trackProjection.js +2 -0
  201. package/dist/runstore/workspaceLayout.d.ts +13 -0
  202. package/dist/runstore/workspaceLayout.js +76 -0
  203. package/dist/runtime/completionCheckRunner.d.ts +3 -0
  204. package/dist/runtime/completionCheckRunner.js +45 -7
  205. package/dist/runtime/containerLimits.d.ts +16 -0
  206. package/dist/runtime/containerLimits.js +84 -0
  207. package/dist/runtime/credentialMaterialisation.d.ts +28 -0
  208. package/dist/runtime/credentialMaterialisation.js +128 -0
  209. package/dist/runtime/gitCheckoutCapability.js +7 -13
  210. package/dist/runtime/pipelineRunner.d.ts +36 -1
  211. package/dist/runtime/pipelineRunner.js +178 -17
  212. package/dist/runtime/pipelineScheduler.d.ts +15 -0
  213. package/dist/runtime/pipelineScheduler.js +135 -27
  214. package/dist/runtime/reloadRunCatalog.js +6 -1
  215. package/dist/runtime/repositoryMaterialize.d.ts +47 -0
  216. package/dist/runtime/repositoryMaterialize.js +235 -0
  217. package/dist/runtime/resumeTimedOut.d.ts +1 -1
  218. package/dist/runtime/resumeTimedOut.js +5 -2
  219. package/dist/runtime/runChangeBus.js +9 -0
  220. package/dist/runtime/runDeletion.d.ts +7 -0
  221. package/dist/runtime/runDeletion.js +37 -0
  222. package/dist/runtime/runManager.d.ts +158 -4
  223. package/dist/runtime/runManager.js +1596 -79
  224. package/dist/runtime/runRetentionSweep.d.ts +31 -0
  225. package/dist/runtime/runRetentionSweep.js +328 -0
  226. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  227. package/dist/runtime/runRetryCoordinator.js +19 -2
  228. package/dist/runtime/runSkills.d.ts +40 -0
  229. package/dist/runtime/runSkills.js +173 -0
  230. package/dist/runtime/stageAttemptBootstrap.d.ts +5 -1
  231. package/dist/runtime/stageAttemptBootstrap.js +195 -24
  232. package/dist/runtime/stageCacheEnv.d.ts +6 -0
  233. package/dist/runtime/stageCacheEnv.js +34 -0
  234. package/dist/runtime/stageConcurrency.d.ts +2 -1
  235. package/dist/runtime/stageConcurrency.js +4 -2
  236. package/dist/runtime/stageEnvironment.d.ts +38 -0
  237. package/dist/runtime/stageEnvironment.js +193 -0
  238. package/dist/runtime/stageProcessLauncher.d.ts +18 -0
  239. package/dist/runtime/stageProcessLauncher.js +194 -38
  240. package/dist/runtime/stageRecovery.d.ts +3 -1
  241. package/dist/runtime/stageRecovery.js +19 -4
  242. package/dist/runtime/stageRoots.d.ts +38 -1
  243. package/dist/runtime/stageRoots.js +118 -0
  244. package/dist/runtime/stageRunner.d.ts +1 -0
  245. package/dist/runtime/stageRunner.js +3 -1
  246. package/dist/runtime/stageSecretDecl.d.ts +9 -0
  247. package/dist/runtime/stageSecretDecl.js +102 -0
  248. package/dist/runtime/stageSecrets.d.ts +40 -0
  249. package/dist/runtime/stageSecrets.js +181 -0
  250. package/dist/runtime/stageWorker.d.ts +3 -2
  251. package/dist/runtime/stageWorker.js +58 -10
  252. package/dist/runtime/stageWorkerProtocol.d.ts +5 -0
  253. package/dist/runtime/stageWorkerProtocol.js +8 -0
  254. package/dist/runtime/startPayload.d.ts +32 -0
  255. package/dist/runtime/startPayload.js +59 -0
  256. package/dist/runtime/taskInput.js +14 -5
  257. package/dist/runtime/verifiedStageExecution.d.ts +1 -0
  258. package/dist/runtime/verifiedStageExecution.js +2 -1
  259. package/dist/runtime/workspaceBinding.d.ts +20 -0
  260. package/dist/runtime/workspaceBinding.js +60 -0
  261. package/dist/server/allowedHosts.d.ts +19 -0
  262. package/dist/server/allowedHosts.js +179 -0
  263. package/dist/server/bootstrap.d.ts +36 -2
  264. package/dist/server/bootstrap.js +209 -5
  265. package/dist/server/controlToken.d.ts +47 -0
  266. package/dist/server/controlToken.js +241 -0
  267. package/dist/server/createHttpHost.d.ts +11 -2
  268. package/dist/server/createHttpHost.js +103 -10
  269. package/dist/server/ensureGlobalService.d.ts +5 -1
  270. package/dist/server/ensureGlobalService.js +35 -1
  271. package/dist/server/healthSurfaces.d.ts +6 -0
  272. package/dist/server/healthSurfaces.js +98 -0
  273. package/dist/server/http.d.ts +12 -1
  274. package/dist/server/http.js +1085 -485
  275. package/dist/server/listenHost.d.ts +5 -0
  276. package/dist/server/listenHost.js +56 -0
  277. package/dist/server/mcpHost.d.ts +10 -1
  278. package/dist/server/mcpHost.js +36 -6
  279. package/dist/server/operatorResults.d.ts +2 -2
  280. package/dist/server/operatorResults.js +40 -29
  281. package/dist/server/requestAuthContext.d.ts +12 -0
  282. package/dist/server/requestAuthContext.js +21 -0
  283. package/dist/server/shutdown.d.ts +69 -0
  284. package/dist/server/shutdown.js +219 -0
  285. package/dist/types/pipeline.d.ts +7 -0
  286. package/dist/types/stage.d.ts +8 -0
  287. package/dist/types/task.d.ts +8 -0
  288. package/dist/ui/assets/index-B6hlSXXn.js +120 -0
  289. package/dist/ui/index.html +1 -1
  290. package/examples/README.md +98 -0
  291. package/examples/archify-on-pr/README.md +213 -0
  292. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +19 -0
  293. package/examples/archify-on-pr/archify-on-pr.task.yaml +19 -0
  294. package/examples/archify-on-pr/author-diagrams.yaml +106 -0
  295. package/examples/archify-on-pr/detect-changes.yaml +89 -0
  296. package/examples/ci-validate/README.md +35 -0
  297. package/examples/ci-validate/check.yaml +13 -0
  298. package/examples/ci-validate/ci-demo.pipeline.yaml +4 -0
  299. package/examples/ci-validate/validate.sh +5 -0
  300. package/examples/compliance-review-demo/README.md +125 -0
  301. package/examples/compliance-review-demo/compliance-review-demo.pipeline.yaml +29 -0
  302. package/examples/compliance-review-demo/connect-account.yaml +47 -0
  303. package/examples/compliance-review-demo/map-to-control.yaml +123 -0
  304. package/examples/compliance-review-demo/poll-bucket-config.yaml +103 -0
  305. package/examples/compliance-review-demo/publish-report.yaml +79 -0
  306. package/examples/compliance-review-demo/review-verdict.yaml +122 -0
  307. package/examples/compliance-review-demo/run-compliance-check.yaml +96 -0
  308. package/examples/compliance-review-demo/store-evidence.yaml +143 -0
  309. package/examples/conditional-fork/README.md +89 -0
  310. package/examples/conditional-fork/branch-a.yaml +13 -0
  311. package/examples/conditional-fork/branch-b.yaml +13 -0
  312. package/examples/conditional-fork/decide.yaml +35 -0
  313. package/examples/conditional-fork/fork-demo.pipeline.yaml +20 -0
  314. package/examples/conditional-fork/fork-demo.task.yaml +8 -0
  315. package/examples/context7-mcp/README.md +42 -0
  316. package/examples/context7-mcp/context7-mcp.pipeline.yaml +15 -0
  317. package/examples/context7-mcp/context7-mcp.task.yaml +4 -0
  318. package/examples/context7-mcp/fetch-docs.yaml +52 -0
  319. package/examples/context7-mcp/resolve-library.yaml +31 -0
  320. package/examples/context7-mcp/write-brief.yaml +52 -0
  321. package/examples/feature-loop/README.md +104 -0
  322. package/examples/feature-loop/address-feedback.yaml +148 -0
  323. package/examples/feature-loop/align.yaml +116 -0
  324. package/examples/feature-loop/decompose.yaml +86 -0
  325. package/examples/feature-loop/default-model-hierarchy.task.yaml +58 -0
  326. package/examples/feature-loop/feature-loop.pipeline.yaml +140 -0
  327. package/examples/feature-loop/feature-loop.task.yaml +25 -0
  328. package/examples/feature-loop/implement.yaml +128 -0
  329. package/examples/feature-loop/plan.yaml +111 -0
  330. package/examples/feature-loop/publish.yaml +167 -0
  331. package/examples/feature-loop/review.yaml +146 -0
  332. package/examples/feature-loop/verify.yaml +106 -0
  333. package/examples/feedback-loop/README.md +72 -0
  334. package/examples/feedback-loop/VERIFY.md +208 -0
  335. package/examples/feedback-loop/continue-only.task.yaml +4 -0
  336. package/examples/feedback-loop/exhaust-require-continue.task.yaml +6 -0
  337. package/examples/feedback-loop/exhaust-wait-human.task.yaml +11 -0
  338. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +23 -0
  339. package/examples/feedback-loop/feedback-loop.pipeline.yaml +23 -0
  340. package/examples/feedback-loop/feedback-loop.task.yaml +4 -0
  341. package/examples/feedback-loop/implement.yaml +14 -0
  342. package/examples/feedback-loop/plan.yaml +14 -0
  343. package/examples/feedback-loop/review.yaml +15 -0
  344. package/examples/feedback-loop/send-back-once.task.yaml +6 -0
  345. package/examples/feedback-loop/submit.yaml +12 -0
  346. package/examples/generic-fan-in/README.md +151 -0
  347. package/examples/generic-fan-in/clarify.yaml +18 -0
  348. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +19 -0
  349. package/examples/generic-fan-in/generic-fan-in-accepted.task.yaml +8 -0
  350. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +18 -0
  351. package/examples/generic-fan-in/generic-fan-in.task.yaml +9 -0
  352. package/examples/generic-fan-in/research.yaml +45 -0
  353. package/examples/generic-fan-in/synthesize.yaml +66 -0
  354. package/examples/generic-fan-in/validation.yaml +45 -0
  355. package/examples/github-release/README.md +78 -0
  356. package/examples/github-release/draft-release-notes.yaml +64 -0
  357. package/examples/github-release/github-release.pipeline.yaml +11 -0
  358. package/examples/github-release/github-release.task.yaml +13 -0
  359. package/examples/github-release/publish-github-release.yaml +64 -0
  360. package/examples/hello-world/README.md +28 -0
  361. package/examples/hello-world/hello.pipeline.yaml +4 -0
  362. package/examples/hello-world/my-task.task.yaml +6 -0
  363. package/examples/hello-world/research.yaml +18 -0
  364. package/examples/mcp-hitl-tour/README.md +113 -0
  365. package/examples/mcp-hitl-tour/draft-angle.yaml +33 -0
  366. package/examples/mcp-hitl-tour/draft-short.yaml +32 -0
  367. package/examples/mcp-hitl-tour/emit-angles.yaml +45 -0
  368. package/examples/mcp-hitl-tour/gather.yaml +31 -0
  369. package/examples/mcp-hitl-tour/intake.yaml +62 -0
  370. package/examples/mcp-hitl-tour/mcp-hitl-tour-exhaust.task.yaml +13 -0
  371. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +54 -0
  372. package/examples/mcp-hitl-tour/mcp-hitl-tour.task.yaml +16 -0
  373. package/examples/mcp-hitl-tour/polish.yaml +33 -0
  374. package/examples/mcp-hitl-tour/review.yaml +17 -0
  375. package/examples/mcp-hitl-tour/ship.yaml +25 -0
  376. package/examples/oss-issue-contribution/README.md +218 -0
  377. package/examples/oss-issue-contribution/oss-address-review-feedback.yaml +117 -0
  378. package/examples/oss-issue-contribution/oss-approve-contribution.yaml +181 -0
  379. package/examples/oss-issue-contribution/oss-approve-plan.yaml +111 -0
  380. package/examples/oss-issue-contribution/oss-explain-fix.yaml +108 -0
  381. package/examples/oss-issue-contribution/oss-explain-issue.yaml +114 -0
  382. package/examples/oss-issue-contribution/oss-implement-source-fix.yaml +171 -0
  383. package/examples/oss-issue-contribution/oss-investigate-area.yaml +116 -0
  384. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +125 -0
  385. package/examples/oss-issue-contribution/oss-issue-intake.yaml +106 -0
  386. package/examples/oss-issue-contribution/oss-plan-investigation.yaml +112 -0
  387. package/examples/oss-issue-contribution/oss-plan-review.yaml +109 -0
  388. package/examples/oss-issue-contribution/oss-reproduce-issue.yaml +140 -0
  389. package/examples/oss-issue-contribution/oss-review-change.yaml +107 -0
  390. package/examples/oss-issue-contribution/oss-verify-fix.yaml +130 -0
  391. package/examples/oss-issue-contribution/oss-write-regression-test.yaml +135 -0
  392. package/examples/oss-issue-contribution/pi-7629.task.yaml +79 -0
  393. package/examples/oss-issue-contribution/pi-8919.task.yaml +75 -0
  394. package/examples/oss-issue-contribution/pydantic-ai-7971.task.yaml +53 -0
  395. package/examples/oss-quick-fix/README.md +65 -0
  396. package/examples/oss-quick-fix/implement-fix.yaml +84 -0
  397. package/examples/oss-quick-fix/oss-quick-fix.pipeline.yaml +35 -0
  398. package/examples/oss-quick-fix/oss-quick-fix.task.yaml +19 -0
  399. package/examples/oss-quick-fix/ship-fix.yaml +81 -0
  400. package/examples/oss-quick-fix/understand-issue.yaml +67 -0
  401. package/examples/oss-quick-fix/verify-fix.yaml +73 -0
  402. package/examples/plan-review/README.md +37 -0
  403. package/examples/plan-review/plan-review-followup.yaml +13 -0
  404. package/examples/plan-review/plan-review.pipeline.yaml +9 -0
  405. package/examples/plan-review/plan-review.task.yaml +9 -0
  406. package/examples/plan-review/plan-review.yaml +25 -0
  407. package/examples/playwright-mcp/README.md +39 -0
  408. package/examples/playwright-mcp/playwright-mcp.pipeline.yaml +5 -0
  409. package/examples/playwright-mcp/playwright-mcp.task.yaml +4 -0
  410. package/examples/playwright-mcp/screenshot.yaml +40 -0
  411. package/examples/retry-tour/README.md +75 -0
  412. package/examples/retry-tour/retry-tour.pipeline.yaml +122 -0
  413. package/examples/retry-tour/retry-tour.task.yaml +9 -0
  414. package/examples/route-if-tour/README.md +202 -0
  415. package/examples/route-if-tour/route-if-tour.pipeline.yaml +950 -0
  416. package/examples/route-if-tour/route-if-tour.task.yaml +4 -0
  417. package/examples/route-wiring-smoke-test/01-core-routing.pipeline.yaml +168 -0
  418. package/examples/route-wiring-smoke-test/02-on-gating.pipeline.yaml +44 -0
  419. package/examples/route-wiring-smoke-test/03-two-entry-points.pipeline.yaml +28 -0
  420. package/examples/route-wiring-smoke-test/04-entry-false-equivalent-to-omitted.pipeline.yaml +27 -0
  421. package/examples/route-wiring-smoke-test/05-fork-choice.pipeline.yaml +110 -0
  422. package/examples/route-wiring-smoke-test/06-fan-out-fan-in.pipeline.yaml +82 -0
  423. package/examples/route-wiring-smoke-test/07-loop-human-decision.pipeline.yaml +82 -0
  424. package/examples/route-wiring-smoke-test/08-skip-fallback-and-multi-on-gate.pipeline.yaml +77 -0
  425. package/examples/route-wiring-smoke-test/09-multi-loop-targets.pipeline.yaml +102 -0
  426. package/examples/route-wiring-smoke-test/10-uses-dialect-fork.pipeline.yaml +23 -0
  427. package/examples/route-wiring-smoke-test/11-sequential-io-handoff.pipeline.yaml +37 -0
  428. package/examples/route-wiring-smoke-test/12-complex-io-schemas.pipeline.yaml +138 -0
  429. package/examples/route-wiring-smoke-test/13-ref-io-handoff.pipeline.yaml +84 -0
  430. package/examples/route-wiring-smoke-test/14-if-eq-gating.pipeline.yaml +50 -0
  431. package/examples/route-wiring-smoke-test/15-if-all-gated.pipeline.yaml +37 -0
  432. package/examples/route-wiring-smoke-test/16-if-composition.pipeline.yaml +65 -0
  433. package/examples/route-wiring-smoke-test/17-if-join.pipeline.yaml +80 -0
  434. package/examples/route-wiring-smoke-test/README.md +377 -0
  435. package/examples/route-wiring-smoke-test/rejected/01-reject-legacy-needs.pipeline.yaml +26 -0
  436. package/examples/route-wiring-smoke-test/rejected/02-reject-legacy-fork.pipeline.yaml +25 -0
  437. package/examples/route-wiring-smoke-test/rejected/03-reject-legacy-feedback-loop.pipeline.yaml +30 -0
  438. package/examples/route-wiring-smoke-test/rejected/04-reject-missing-entry.pipeline.yaml +24 -0
  439. package/examples/route-wiring-smoke-test/rejected/05-reject-unreachable.pipeline.yaml +35 -0
  440. package/examples/route-wiring-smoke-test/rejected/06-reject-allow-none-without-route-select.pipeline.yaml +37 -0
  441. package/examples/route-wiring-smoke-test/rejected/07-reject-cycle.pipeline.yaml +27 -0
  442. package/examples/route-wiring-smoke-test/rejected/08-reject-loop-non-ancestor.pipeline.yaml +42 -0
  443. package/examples/route-wiring-smoke-test/rejected/09-reject-route-select-leaf.pipeline.yaml +26 -0
  444. package/examples/route-wiring-smoke-test/rejected/12-reject-loop-replay-unsafe.pipeline.yaml +55 -0
  445. package/examples/route-wiring-smoke-test/rejected/13-reject-multiple-loop-entries.pipeline.yaml +51 -0
  446. package/examples/route-wiring-smoke-test/rejected/14-reject-duplicate-stage-id.pipeline.yaml +35 -0
  447. package/examples/route-wiring-smoke-test/rejected/15-reject-unknown-route-target.pipeline.yaml +15 -0
  448. package/examples/route-wiring-smoke-test/rejected/16-reject-malformed-route-item.pipeline.yaml +15 -0
  449. package/examples/route-wiring-smoke-test/rejected/17-reject-io-incompatible.pipeline.yaml +33 -0
  450. package/examples/route-wiring-smoke-test/rejected/18-reject-nested-io.pipeline.yaml +51 -0
  451. package/examples/route-wiring-smoke-test/rejected/19-reject-array-item-io.pipeline.yaml +55 -0
  452. package/examples/route-wiring-smoke-test/rejected/20-reject-closed-io.pipeline.yaml +36 -0
  453. package/examples/route-wiring-smoke-test/rejected/21-reject-ref-io.pipeline.yaml +42 -0
  454. package/examples/route-wiring-smoke-test/rejected/22-reject-if-unknown-field.pipeline.yaml +33 -0
  455. package/examples/route-wiring-smoke-test/rejected/23-reject-if-optional-field.pipeline.yaml +32 -0
  456. package/examples/route-wiring-smoke-test/rejected/24-reject-if-empty-all.pipeline.yaml +31 -0
  457. package/examples/route-wiring-smoke-test/rejected/25-reject-if-gt-on-string.pipeline.yaml +33 -0
  458. package/examples/route-wiring-smoke-test/rejected/26-reject-if-optional-nested.pipeline.yaml +36 -0
  459. package/examples/route-wiring-smoke-test/rejected/27-reject-if-on-loop.pipeline.yaml +63 -0
  460. package/examples/route-wiring-smoke-test/rejected/29-reject-if-on-failed.pipeline.yaml +46 -0
  461. package/examples/route-wiring-smoke-test/smoke-test.task.yaml +4 -0
  462. package/examples/route-wiring-smoke-test/stages/branch-a.yaml +12 -0
  463. package/examples/route-wiring-smoke-test/stages/branch-b.yaml +12 -0
  464. package/examples/route-wiring-smoke-test/stages/decide.yaml +12 -0
  465. package/examples/route-wiring-smoke-test/stages/finish.yaml +12 -0
  466. package/examples/ship-feature/README.md +190 -0
  467. package/examples/ship-feature/feat-address-feedback.yaml +119 -0
  468. package/examples/ship-feature/feat-implement.yaml +180 -0
  469. package/examples/ship-feature/feat-plan.yaml +120 -0
  470. package/examples/ship-feature/feat-review.yaml +109 -0
  471. package/examples/ship-feature/feat-ship.yaml +203 -0
  472. package/examples/ship-feature/ship-feature.pipeline.yaml +44 -0
  473. package/examples/ship-feature/ship-feature.task.yaml +78 -0
  474. package/examples/stage-mcp/README.md +144 -0
  475. package/examples/stage-mcp/echo-mcp.mjs +20 -0
  476. package/examples/stage-mcp/stage-mcp-dead.pipeline.yaml +5 -0
  477. package/examples/stage-mcp/stage-mcp-dead.task.yaml +3 -0
  478. package/examples/stage-mcp/stage-mcp-missing-var.pipeline.yaml +5 -0
  479. package/examples/stage-mcp/stage-mcp-missing-var.task.yaml +3 -0
  480. package/examples/stage-mcp/stage-mcp.pipeline.yaml +5 -0
  481. package/examples/stage-mcp/stage-mcp.task.yaml +4 -0
  482. package/examples/stage-mcp/use-echo.yaml +20 -0
  483. package/examples/verify-tour/README.md +80 -0
  484. package/examples/verify-tour/verify-tour.pipeline.yaml +162 -0
  485. package/examples/verify-tour/verify-tour.task.yaml +11 -0
  486. package/package.json +5 -1
  487. package/skills/stageflow/SKILL.md +6 -6
  488. package/skills/stageflow/references/control-surface.md +11 -5
  489. package/skills/stageflow/scripts/detect-host.mjs +14 -1
  490. package/skills/stageflow/scripts/detect-host.test.mjs +29 -1
  491. package/skills/stageflow-author/SKILL.md +4 -3
  492. package/skills/stageflow-author/references/catalog-mapping.md +16 -0
  493. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  494. package/skills/stageflow-run/SKILL.md +20 -12
  495. package/skills/stageflow-run/references/mcp-call.md +9 -1
  496. package/skills/stageflow-run/references/task-and-pipeline-selection.md +16 -5
  497. package/skills/stageflow-run/scripts/mcp-call.mjs +46 -6
  498. package/skills/stageflow-run/scripts/mcp-call.test.mjs +64 -0
  499. package/skills/stageflow-session-capture/references/catalog-authoring.md +2 -0
  500. package/skills/stageflow-setup/SKILL.md +40 -8
  501. package/dist/ui/assets/index-Da5EHU6M.js +0 -119
@@ -8,6 +8,12 @@ export type ModelSelection = {
8
8
  /** Stage yaml / inline `model`; overrides pipeline/global. */
9
9
  stage?: string;
10
10
  };
11
+ export type ModelAuthoredTier = "stage" | "pipeline" | "global";
12
+ export type ResolvedModelSelection = {
13
+ model: string;
14
+ tier: ModelAuthoredTier;
15
+ };
16
+ export declare function modelAuthoredTier(selection: ModelSelection): ModelAuthoredTier;
11
17
  /**
12
18
  * stage > pipeline > global. No silent hardcoded fallback — unset is a load error.
13
19
  * Present-but-empty / whitespace follows parseModelField (load failure, not "").
@@ -15,6 +21,6 @@ export type ModelSelection = {
15
21
  export declare function resolveModelOutcome(selection: ModelSelection, ctx: {
16
22
  stageId: string;
17
23
  pipelineId: string;
18
- }): LoadOutcome<string>;
24
+ }): LoadOutcome<ResolvedModelSelection>;
19
25
  /** The global tier's model id from an already-loaded manifest, or undefined when missing/unset. */
20
26
  export declare function globalModelFromManifest(loaded: LoadedManifest | null): string | undefined;
@@ -1,5 +1,12 @@
1
1
  import { loadFailure, loadSuccess } from "./loadOutcome.js";
2
2
  import { parseModelField } from "./modelField.js";
3
+ export function modelAuthoredTier(selection) {
4
+ if (selection.stage !== undefined)
5
+ return "stage";
6
+ if (selection.pipeline !== undefined)
7
+ return "pipeline";
8
+ return "global";
9
+ }
3
10
  /**
4
11
  * stage > pipeline > global. No silent hardcoded fallback — unset is a load error.
5
12
  * Present-but-empty / whitespace follows parseModelField (load failure, not "").
@@ -27,7 +34,10 @@ export function resolveModelOutcome(selection, ctx) {
27
34
  },
28
35
  ]);
29
36
  }
30
- return loadSuccess(parsed.value);
37
+ return loadSuccess({
38
+ model: parsed.value,
39
+ tier: modelAuthoredTier(selection),
40
+ });
31
41
  }
32
42
  /** The global tier's model id from an already-loaded manifest, or undefined when missing/unset. */
33
43
  export function globalModelFromManifest(loaded) {
@@ -1,4 +1,4 @@
1
- export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "connect_failed";
1
+ export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "untrusted_config_origin" | "connect_failed";
2
2
  export declare class StageMcpError extends Error {
3
3
  readonly code: StageMcpErrorCode;
4
4
  constructor(message: string, code: StageMcpErrorCode);
@@ -15,6 +15,7 @@ export type InspectedMcpCatalog = {
15
15
  };
16
16
  export declare function mcpCatalogPath(projectRoot: string): string;
17
17
  export declare function parseMcpCatalog(raw: string, catalogPath?: string): McpCatalogServers;
18
+ export declare function mcpCatalogExists(projectRoot: string): Promise<boolean>;
18
19
  export declare function loadMcpCatalog(projectRoot: string): Promise<InspectedMcpCatalog>;
19
20
  export type ProjectMcpCatalogTransport = "stdio" | "http";
20
21
  export type ProjectMcpCatalogEntry = {
@@ -32,4 +33,11 @@ export declare function resolveStageMcpServers(options: {
32
33
  projectRoot: string;
33
34
  allowlist?: readonly string[];
34
35
  env: NodeJS.ProcessEnv;
36
+ stageId?: string;
37
+ bindingKind?: "repository" | "checkout" | "none";
38
+ trustWorkspaceConfig?: string[];
39
+ /** Host project root used for trust_workspace_config allowlist matching. */
40
+ trustProjectRoot?: string;
41
+ /** When true, .mcp.json is treated as workspace-sourced. */
42
+ workspaceSourced?: boolean;
35
43
  }): Promise<ResolvedMcpServers>;
@@ -51,6 +51,18 @@ export function parseMcpCatalog(raw, catalogPath = MCP_CATALOG_FILENAME) {
51
51
  }
52
52
  return servers;
53
53
  }
54
+ export async function mcpCatalogExists(projectRoot) {
55
+ try {
56
+ await readFile(mcpCatalogPath(projectRoot), "utf8");
57
+ return true;
58
+ }
59
+ catch (err) {
60
+ const code = err.code;
61
+ if (code === "ENOENT")
62
+ return false;
63
+ throw err;
64
+ }
65
+ }
54
66
  export async function loadMcpCatalog(projectRoot) {
55
67
  const catalogPath = mcpCatalogPath(projectRoot);
56
68
  let raw;
@@ -108,7 +120,7 @@ export function assertMcpAllowlistKnown(servers, allowlist) {
108
120
  const INTERPOLATION_TOKEN = /\$\{([^}]*)\}/g;
109
121
  const ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
110
122
  const ENV_VAR_WITH_DEFAULT = /^([A-Za-z_][A-Za-z0-9_]*):-(.*)$/;
111
- function interpolateString(value, env, serverName) {
123
+ function interpolateString(value, env, serverName, stageId) {
112
124
  return value.replace(INTERPOLATION_TOKEN, (_match, inner) => {
113
125
  const withDefault = ENV_VAR_WITH_DEFAULT.exec(inner);
114
126
  if (withDefault) {
@@ -120,24 +132,27 @@ function interpolateString(value, env, serverName) {
120
132
  if (found !== undefined) {
121
133
  return found;
122
134
  }
123
- throw new StageMcpError(`Unresolved MCP catalog variable "${inner}"`, "unresolved_var");
135
+ const stagePart = stageId !== undefined ? ` (stage "${stageId}")` : "";
136
+ throw new StageMcpError(`Unresolved MCP catalog variable "${inner}" for server "${serverName}"${stagePart}. Declare it in secrets: (use as: env for tokens needed in MCP env) or use \${${inner}:-default}.`, "unresolved_var");
124
137
  }
125
138
  throw new StageMcpError(`MCP catalog server "${serverName}" has an invalid interpolation form`, "invalid_config");
126
139
  });
127
140
  }
128
- function interpolateField(key, value, env, serverName) {
141
+ function interpolateField(key, value, env, serverName, stageId) {
129
142
  if ((key === "command" || key === "url" || key === "cwd") && typeof value === "string") {
130
- return interpolateString(value, env, serverName);
143
+ return interpolateString(value, env, serverName, stageId);
131
144
  }
132
145
  if (key === "args" && Array.isArray(value)) {
133
- return value.map((item) => typeof item === "string" ? interpolateString(item, env, serverName) : item);
146
+ return value.map((item) => typeof item === "string"
147
+ ? interpolateString(item, env, serverName, stageId)
148
+ : item);
134
149
  }
135
150
  if ((key === "env" || key === "headers") && isPlainObject(value)) {
136
151
  const copied = {};
137
152
  for (const [field, fieldValue] of Object.entries(value)) {
138
153
  copied[field] =
139
154
  typeof fieldValue === "string"
140
- ? interpolateString(fieldValue, env, serverName)
155
+ ? interpolateString(fieldValue, env, serverName, stageId)
141
156
  : fieldValue;
142
157
  }
143
158
  return copied;
@@ -185,10 +200,10 @@ function stampSpawnRoot(resolved, projectRoot, serverName) {
185
200
  }
186
201
  return resolved;
187
202
  }
188
- function interpolateServer(entry, env, serverName, projectRoot) {
203
+ function interpolateServer(entry, env, serverName, projectRoot, stageId) {
189
204
  const resolved = {};
190
205
  for (const [key, value] of Object.entries(entry)) {
191
- resolved[key] = interpolateField(key, value, env, serverName);
206
+ resolved[key] = interpolateField(key, value, env, serverName, stageId);
192
207
  }
193
208
  return stampSpawnRoot(resolved, projectRoot, serverName);
194
209
  }
@@ -197,11 +212,23 @@ export async function resolveStageMcpServers(options) {
197
212
  if (allowlist.length === 0) {
198
213
  return {};
199
214
  }
215
+ if (options.workspaceSourced === true) {
216
+ const { decideWorkspaceConfigTrust } = await import("./configOrigin.js");
217
+ const decision = decideWorkspaceConfigTrust({
218
+ bindingKind: options.bindingKind ?? "none",
219
+ projectRoot: options.trustProjectRoot ?? options.projectRoot,
220
+ trustWorkspaceConfig: options.trustWorkspaceConfig,
221
+ source: "workspace",
222
+ });
223
+ if (!decision.allow) {
224
+ throw new StageMcpError(decision.message ?? "untrusted_config_origin", "untrusted_config_origin");
225
+ }
226
+ }
200
227
  const catalog = await loadMcpCatalog(options.projectRoot);
201
228
  assertMcpAllowlistKnown(catalog.servers, allowlist);
202
229
  const resolved = {};
203
230
  for (const name of allowlist) {
204
- resolved[name] = interpolateServer(catalog.servers[name], options.env, name, options.projectRoot);
231
+ resolved[name] = interpolateServer(catalog.servers[name], options.env, name, options.projectRoot, options.stageId);
205
232
  }
206
233
  return resolved;
207
234
  }
@@ -0,0 +1,9 @@
1
+ export declare class SecretFromEnvError extends Error {
2
+ readonly code = "config_invalid";
3
+ constructor(message: string);
4
+ }
5
+ /**
6
+ * Read a secret from `NAME` or `NAME_FILE`. Both set → error.
7
+ * File values trim exactly one trailing newline (`\n` or `\r\n`).
8
+ */
9
+ export declare function readSecretFromEnvOrFile(env: NodeJS.ProcessEnv, name: string): string | undefined;
@@ -0,0 +1,34 @@
1
+ import { readFileSync } from "node:fs";
2
+ export class SecretFromEnvError extends Error {
3
+ code = "config_invalid";
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "SecretFromEnvError";
7
+ }
8
+ }
9
+ /**
10
+ * Read a secret from `NAME` or `NAME_FILE`. Both set → error.
11
+ * File values trim exactly one trailing newline (`\n` or `\r\n`).
12
+ */
13
+ export function readSecretFromEnvOrFile(env, name) {
14
+ const fileKey = `${name}_FILE`;
15
+ const plain = env[name];
16
+ const filePath = env[fileKey];
17
+ const hasPlain = plain !== undefined && plain.length > 0;
18
+ const hasFile = filePath !== undefined && filePath.length > 0;
19
+ if (hasPlain && hasFile) {
20
+ throw new SecretFromEnvError(`Set only one of ${name} or ${fileKey}, not both`);
21
+ }
22
+ if (hasFile) {
23
+ try {
24
+ return readFileSync(filePath, "utf8").replace(/\r?\n$/, "");
25
+ }
26
+ catch (err) {
27
+ const detail = err instanceof Error ? err.message : String(err);
28
+ throw new SecretFromEnvError(`Failed to read ${fileKey}=${filePath}: ${detail}`);
29
+ }
30
+ }
31
+ if (hasPlain)
32
+ return plain;
33
+ return undefined;
34
+ }
@@ -0,0 +1,11 @@
1
+ /** Symbolic wire id for the packaged examples catalog root. */
2
+ export declare const SEEDED_EXAMPLES_ID = "examples";
3
+ /**
4
+ * Absolute path to packaged `examples/` when present next to the package root.
5
+ * Does not git-init; callers build CatalogContext with fixed projectRoot + manifest.
6
+ */
7
+ export declare function resolveSeededExamplesPath(fromUrl?: string): string | undefined;
8
+ export declare function defaultSeededRoots(): Array<{
9
+ id: string;
10
+ path: string;
11
+ }>;
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { existsSync } from "node:fs";
4
+ /** Symbolic wire id for the packaged examples catalog root. */
5
+ export const SEEDED_EXAMPLES_ID = "examples";
6
+ /**
7
+ * Absolute path to packaged `examples/` when present next to the package root.
8
+ * Does not git-init; callers build CatalogContext with fixed projectRoot + manifest.
9
+ */
10
+ export function resolveSeededExamplesPath(fromUrl = import.meta.url) {
11
+ const here = path.dirname(fileURLToPath(fromUrl));
12
+ const candidates = [
13
+ path.resolve(here, "../../examples"),
14
+ path.resolve(here, "../../../examples"),
15
+ path.resolve(process.cwd(), "examples"),
16
+ ];
17
+ for (const candidate of candidates) {
18
+ if (existsSync(path.join(candidate, "README.md"))) {
19
+ return candidate;
20
+ }
21
+ }
22
+ return undefined;
23
+ }
24
+ export function defaultSeededRoots() {
25
+ const examples = resolveSeededExamplesPath();
26
+ if (examples === undefined)
27
+ return [];
28
+ return [{ id: SEEDED_EXAMPLES_ID, path: examples }];
29
+ }
@@ -0,0 +1,24 @@
1
+ import { type LoadOutcome } from "./loadOutcome.js";
2
+ export type ToolRequirement = {
3
+ tool: string;
4
+ version?: string;
5
+ };
6
+ export declare function parseToolRequires(raw: unknown, label: string, options: {
7
+ code: "pipeline.invalid_requires" | "stage.invalid_requires";
8
+ category: "pipeline" | "stage";
9
+ pipelineId?: string;
10
+ stageId?: string;
11
+ }): LoadOutcome<ToolRequirement[] | undefined>;
12
+ /**
13
+ * Merge requirement lists: same tool → intersect version ranges (stricter).
14
+ * Non-intersecting ranges fail with pipeline.requires_conflict.
15
+ */
16
+ export declare function mergeToolRequires(lists: Array<readonly ToolRequirement[] | undefined>, ctx: {
17
+ pipelineId: string;
18
+ label?: string;
19
+ }): LoadOutcome<ToolRequirement[]>;
20
+ export declare function collectEffectiveRequires(options: {
21
+ pipelineRequires?: readonly ToolRequirement[];
22
+ stageRequires: Array<readonly ToolRequirement[] | undefined>;
23
+ pipelineId: string;
24
+ }): LoadOutcome<ToolRequirement[]>;
@@ -0,0 +1,115 @@
1
+ import semver from "semver";
2
+ import { loadFailure, loadSuccess } from "./loadOutcome.js";
3
+ const ALLOWED_KEYS = new Set(["tool", "version"]);
4
+ function isPlainObject(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function requiresIssue(options, message) {
8
+ if (options.category === "pipeline") {
9
+ return {
10
+ code: options.code,
11
+ message,
12
+ category: "pipeline",
13
+ ...(options.pipelineId !== undefined
14
+ ? { pipelineId: options.pipelineId }
15
+ : {}),
16
+ ...(options.stageId !== undefined ? { stageId: options.stageId } : {}),
17
+ };
18
+ }
19
+ return {
20
+ code: options.code,
21
+ message,
22
+ category: "stage",
23
+ ...(options.stageId !== undefined ? { stageId: options.stageId } : {}),
24
+ };
25
+ }
26
+ export function parseToolRequires(raw, label, options) {
27
+ if (raw === undefined)
28
+ return loadSuccess(undefined);
29
+ if (!Array.isArray(raw)) {
30
+ return loadFailure([
31
+ requiresIssue(options, `Invalid ${label}: requires must be an array of { tool, version? }`),
32
+ ]);
33
+ }
34
+ const out = [];
35
+ for (let i = 0; i < raw.length; i++) {
36
+ const item = raw[i];
37
+ if (!isPlainObject(item)) {
38
+ return loadFailure([
39
+ requiresIssue(options, `Invalid ${label}: requires[${i}] must be an object`),
40
+ ]);
41
+ }
42
+ for (const key of Object.keys(item)) {
43
+ if (!ALLOWED_KEYS.has(key)) {
44
+ return loadFailure([
45
+ requiresIssue(options, `Invalid ${label}: requires[${i}] unknown key "${key}" (allowed: tool, version)`),
46
+ ]);
47
+ }
48
+ }
49
+ if (typeof item.tool !== "string" || item.tool.trim() === "") {
50
+ return loadFailure([
51
+ requiresIssue(options, `Invalid ${label}: requires[${i}].tool must be a non-empty string`),
52
+ ]);
53
+ }
54
+ const tool = item.tool.trim();
55
+ if (item.version !== undefined) {
56
+ if (typeof item.version !== "string" || item.version.trim() === "") {
57
+ return loadFailure([
58
+ requiresIssue(options, `Invalid ${label}: requires[${i}].version must be a non-empty semver range string`),
59
+ ]);
60
+ }
61
+ const range = item.version.trim();
62
+ if (!semver.validRange(range)) {
63
+ return loadFailure([
64
+ requiresIssue(options, `Invalid ${label}: requires[${i}].version "${range}" is not a valid semver range`),
65
+ ]);
66
+ }
67
+ out.push({ tool, version: range });
68
+ }
69
+ else {
70
+ out.push({ tool });
71
+ }
72
+ }
73
+ return loadSuccess(out);
74
+ }
75
+ /**
76
+ * Merge requirement lists: same tool → intersect version ranges (stricter).
77
+ * Non-intersecting ranges fail with pipeline.requires_conflict.
78
+ */
79
+ export function mergeToolRequires(lists, ctx) {
80
+ const byTool = new Map();
81
+ for (const list of lists) {
82
+ if (!list)
83
+ continue;
84
+ for (const req of list) {
85
+ const prior = byTool.get(req.tool);
86
+ if (!byTool.has(req.tool)) {
87
+ byTool.set(req.tool, req.version);
88
+ continue;
89
+ }
90
+ if (prior === undefined && req.version === undefined)
91
+ continue;
92
+ if (prior === undefined) {
93
+ byTool.set(req.tool, req.version);
94
+ continue;
95
+ }
96
+ if (req.version === undefined)
97
+ continue;
98
+ if (!semver.intersects(prior, req.version)) {
99
+ return loadFailure([
100
+ {
101
+ code: "pipeline.requires_conflict",
102
+ message: `Pipeline ${ctx.pipelineId}${ctx.label ? ` (${ctx.label})` : ""}: tool "${req.tool}" has non-intersecting version ranges "${prior}" and "${req.version}"`,
103
+ category: "pipeline",
104
+ pipelineId: ctx.pipelineId,
105
+ },
106
+ ]);
107
+ }
108
+ byTool.set(req.tool, `${prior} ${req.version}`);
109
+ }
110
+ }
111
+ return loadSuccess([...byTool.entries()].map(([tool, version]) => version !== undefined ? { tool, version } : { tool }));
112
+ }
113
+ export function collectEffectiveRequires(options) {
114
+ return mergeToolRequires([options.pipelineRequires, ...options.stageRequires], { pipelineId: options.pipelineId });
115
+ }
@@ -2,7 +2,7 @@ import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline
2
2
  import type { LoadIssue } from "./loadOutcome.js";
3
3
  export type ValidationSeverity = "error" | "warning";
4
4
  export type ValidationCategory = "pipeline" | "stage" | "catalog" | "task";
5
- export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.invalid_completion" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml";
5
+ export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.invalid_completion" | "catalog.path_case_mismatch" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "pipeline.invalid_requires" | "pipeline.requires_conflict" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_secrets" | "stage.unknown_secret" | "stage.denied_secret" | "stage.invalid_requires" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mcp_command_missing" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml" | "catalog.stageflow_home_absolute_path";
6
6
  export type ValidationFinding = {
7
7
  severity: ValidationSeverity;
8
8
  code: ValidationFindingCode | string;
@@ -35,6 +35,10 @@ export declare function effectiveSeverity(finding: ValidationFinding, strict: bo
35
35
  export declare function buildValidationResult(scope: ValidationScope, findings: ValidationFinding[], strict: boolean): ValidationResult;
36
36
  export declare function findingsFromLoadIssues(cwd: string, absPath: string, issues: LoadIssue[]): ValidationFinding[];
37
37
  export declare function findingStageIdFilenameMismatch(cwd: string, absPath: string, fileStem: string, declaredId: string): ValidationFinding;
38
+ /** Parent readdir case check — warn by default; error under --strict. */
39
+ export declare function findingPathCaseMismatch(cwd: string, referencedPath: string): Promise<ValidationFinding | null>;
40
+ export declare function collectAbsolutePathCandidates(text: string): string[];
41
+ export declare function isPathUnderStageflowHome(candidate: string, home: string): boolean;
38
42
  export type ValidatePipelineOptions = {
39
43
  cwd?: string;
40
44
  projectRoot?: string;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { globalStageflowHome } from "../project/globalHome.js";
2
3
  import { INLINE_PIPELINE_PATH, loadPipelineFromObjectOutcome, loadPipelineOutcome, resolvePipelinePath, } from "./loadPipeline.js";
3
4
  import { loadStageOutcome } from "./loadStage.js";
4
5
  import { loadTaskOutcome } from "./loadTask.js";
@@ -7,6 +8,9 @@ import { resolveCatalogContext } from "./resolveCatalogContext.js";
7
8
  import { getCatalogScanPaths } from "./browseCatalog.js";
8
9
  import { manifestPathForProject } from "./loadStageflowManifest.js";
9
10
  import { MCP_CATALOG_FILENAME, StageMcpError, assertMcpAllowlistKnown, loadMcpCatalog, mcpCatalogPath, } from "./resolveStageMcpServers.js";
11
+ import { loadSecretRegistry } from "../runtime/stageSecrets.js";
12
+ import { isForeverDeniedSecret } from "../runtime/stageEnvironment.js";
13
+ import { findMissingMcpCommands } from "../preflight/mcpCommands.js";
10
14
  export function relPath(cwd, absPath) {
11
15
  return path.relative(cwd, absPath).replace(/\\/g, "/");
12
16
  }
@@ -160,6 +164,35 @@ export function findingStageIdFilenameMismatch(cwd, absPath, fileStem, declaredI
160
164
  stageId: declaredId,
161
165
  }, "error");
162
166
  }
167
+ /** Parent readdir case check — warn by default; error under --strict. */
168
+ export async function findingPathCaseMismatch(cwd, referencedPath) {
169
+ const { readdir } = await import("node:fs/promises");
170
+ const abs = path.isAbsolute(referencedPath)
171
+ ? referencedPath
172
+ : path.resolve(cwd, referencedPath);
173
+ const parent = path.dirname(abs);
174
+ const base = path.basename(abs);
175
+ let entries;
176
+ try {
177
+ entries = await readdir(parent);
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ const exact = entries.includes(base);
183
+ if (exact)
184
+ return null;
185
+ const match = entries.find((e) => e.toLowerCase() === base.toLowerCase());
186
+ if (match === undefined)
187
+ return null;
188
+ return baseFinding({
189
+ cwd,
190
+ absPath: abs,
191
+ message: `Path casing differs on disk: referenced "${base}" but found "${match}"`,
192
+ code: "catalog.path_case_mismatch",
193
+ category: "catalog",
194
+ }, "warning");
195
+ }
163
196
  async function checkStageIdFilename(cwd, stagePath) {
164
197
  const fileStem = path.basename(stagePath, ".yaml");
165
198
  try {
@@ -197,6 +230,100 @@ function collectMcpAllowlistNames(loaded) {
197
230
  }
198
231
  return [...names];
199
232
  }
233
+ function findingsForStageSecrets(cwd, pipelinePath, loaded) {
234
+ const registry = loadSecretRegistry(process.env);
235
+ const findings = [];
236
+ for (const stage of loaded.stages) {
237
+ for (const decl of stage.secrets ?? []) {
238
+ if (isForeverDeniedSecret(decl.name)) {
239
+ findings.push(findingStageError(cwd, pipelinePath, `secret "${decl.name}" is permanently denied and cannot be granted`, "stage.denied_secret", stage.id));
240
+ continue;
241
+ }
242
+ if (!registry.has(decl.name)) {
243
+ findings.push(findingStageError(cwd, pipelinePath, `unknown secret "${decl.name}" is not in the Host secret registry`, "stage.unknown_secret", stage.id));
244
+ }
245
+ }
246
+ }
247
+ return findings;
248
+ }
249
+ const ABSOLUTE_PATH_IN_TEXT_RE = /(?:^|[\s"'`=:(])(\/(?:[^\s"'`;|&<>()]+))/g;
250
+ const TRAILING_PATH_PUNCT_RE = /[.,:;!?)\]]+$/;
251
+ export function collectAbsolutePathCandidates(text) {
252
+ const found = [];
253
+ const seen = new Set();
254
+ ABSOLUTE_PATH_IN_TEXT_RE.lastIndex = 0;
255
+ let match;
256
+ while ((match = ABSOLUTE_PATH_IN_TEXT_RE.exec(text)) !== null) {
257
+ const raw = match[1]?.replace(TRAILING_PATH_PUNCT_RE, "") ?? "";
258
+ if (!raw || !path.isAbsolute(raw) || seen.has(raw))
259
+ continue;
260
+ seen.add(raw);
261
+ found.push(raw);
262
+ }
263
+ return found;
264
+ }
265
+ export function isPathUnderStageflowHome(candidate, home) {
266
+ if (!path.isAbsolute(candidate))
267
+ return false;
268
+ const resolved = path.resolve(candidate);
269
+ const homeResolved = path.resolve(home);
270
+ return (resolved === homeResolved || resolved.startsWith(`${homeResolved}${path.sep}`));
271
+ }
272
+ function stageSourcePath(loaded, stageId) {
273
+ const source = loaded.stageSources?.[stageId];
274
+ if (source?.kind === "file")
275
+ return source.path;
276
+ return loaded.pipelinePath;
277
+ }
278
+ function stageflowHomeAbsolutePathMessage(stageId, location, absolutePath) {
279
+ return (`Stage "${stageId}" ${location} contains absolute path under STAGEFLOW_HOME ` +
280
+ `("${absolutePath}"). Use STAGEFLOW_CHECKOUT / STAGEFLOW_RUN_WORKSPACE ` +
281
+ `(and related binding env vars) instead of durable-home paths`);
282
+ }
283
+ function findingsForStageflowHomeAbsolutePaths(cwd, loaded) {
284
+ const home = globalStageflowHome();
285
+ const findings = [];
286
+ const seen = new Set();
287
+ const pushFinding = (stageId, absPath, location, absolutePath) => {
288
+ const key = `${stageId}\0${location}\0${absolutePath}`;
289
+ if (seen.has(key))
290
+ return;
291
+ seen.add(key);
292
+ findings.push(baseFinding({
293
+ cwd,
294
+ absPath,
295
+ message: stageflowHomeAbsolutePathMessage(stageId, location, absolutePath),
296
+ code: "catalog.stageflow_home_absolute_path",
297
+ category: "catalog",
298
+ pipelineId: loaded.pipeline.id,
299
+ stageId,
300
+ }, "error"));
301
+ };
302
+ const scanText = (stageId, absPath, location, text) => {
303
+ for (const candidate of collectAbsolutePathCandidates(text)) {
304
+ if (isPathUnderStageflowHome(candidate, home)) {
305
+ pushFinding(stageId, absPath, location, candidate);
306
+ }
307
+ }
308
+ };
309
+ for (const stage of loaded.stages) {
310
+ const absPath = stageSourcePath(loaded, stage.id);
311
+ scanText(stage.id, absPath, "system_prompt", stage.system_prompt);
312
+ }
313
+ for (const node of loaded.dag.nodes) {
314
+ const checks = node.completion?.checks ?? [];
315
+ for (const check of checks) {
316
+ if (check.type !== "command")
317
+ continue;
318
+ const absPath = stageSourcePath(loaded, node.id);
319
+ scanText(node.id, absPath, `verify command "${check.id}" run`, check.run);
320
+ if (typeof check.cwd === "string") {
321
+ scanText(node.id, absPath, `verify command "${check.id}" cwd`, check.cwd);
322
+ }
323
+ }
324
+ }
325
+ return findings;
326
+ }
200
327
  async function findingsForStageMcpCatalog(cwd, loaded) {
201
328
  const allowlist = collectMcpAllowlistNames(loaded);
202
329
  const ctx = await resolveCatalogContext(cwd);
@@ -235,7 +362,17 @@ async function findingsForStageMcpCatalog(cwd, loaded) {
235
362
  }
236
363
  throw err;
237
364
  }
238
- return [];
365
+ const findings = [];
366
+ const serversForPath = {};
367
+ for (const name of allowlist) {
368
+ const entry = catalog.servers[name];
369
+ if (entry)
370
+ serversForPath[name] = entry;
371
+ }
372
+ for (const missing of findMissingMcpCommands(serversForPath, process.env)) {
373
+ findings.push(findingCatalog(cwd, catalogAbsPath, `MCP server "${missing.serverName}" command "${missing.command}" was not found on PATH`, "catalog.mcp_command_missing", "error"));
374
+ }
375
+ return findings;
239
376
  }
240
377
  async function runPipelineValidation(nameOrPath, options) {
241
378
  const { cwd, validateStages } = options;
@@ -274,6 +411,8 @@ async function runPipelineValidation(nameOrPath, options) {
274
411
  findings.push(...findingsFromLoadIssues(cwd, pipelinePath, outcome.issues));
275
412
  }
276
413
  findings.push(...(await findingsForStageMcpCatalog(cwd, outcome.value)));
414
+ findings.push(...findingsForStageSecrets(cwd, pipelinePath, outcome.value));
415
+ findings.push(...findingsForStageflowHomeAbsolutePaths(cwd, outcome.value));
277
416
  if (validateStages && outcome.value.stageSources) {
278
417
  for (const source of Object.values(outcome.value.stageSources)) {
279
418
  if (source.kind === "file") {
@@ -0,0 +1,39 @@
1
+ import type { RunStore } from "../runstore/port.js";
2
+ export type DiagnosticSeverity = "error" | "warn" | "skipped";
3
+ export type DiagnosticCheckResult = {
4
+ id: string;
5
+ ok: boolean;
6
+ severity: DiagnosticSeverity;
7
+ message: string;
8
+ code?: string;
9
+ detail?: Record<string, unknown>;
10
+ };
11
+ export type ReadyzChecks = {
12
+ store_openable: boolean;
13
+ home_writable: boolean;
14
+ migrations_complete: boolean;
15
+ git_present: boolean;
16
+ };
17
+ export type ReadyzResult = {
18
+ ready: boolean;
19
+ checks: ReadyzChecks;
20
+ code?: string;
21
+ };
22
+ export declare function resetReadyzCacheForTests(): void;
23
+ export declare function livezBody(): {
24
+ ok: true;
25
+ status: "live";
26
+ };
27
+ export declare function probeGitVersion(): Promise<string | undefined>;
28
+ export declare function runReadyzChecks(options: {
29
+ store: RunStore;
30
+ homeDir?: string;
31
+ now?: number;
32
+ bypassCache?: boolean;
33
+ }): Promise<ReadyzResult>;
34
+ export type SchemaHealthStub = {
35
+ user_version: number | null;
36
+ schema_migrations: "present" | "absent" | "unknown";
37
+ note?: string;
38
+ };
39
+ export declare function readSchemaHealth(store: RunStore): SchemaHealthStub;