stageflow 0.3.0 → 0.9.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 (164) hide show
  1. package/README.md +44 -14
  2. package/dist/agent/activity.d.ts +2 -0
  3. package/dist/agent/cursorProvider.d.ts +1 -10
  4. package/dist/agent/cursorProvider.js +18 -10
  5. package/dist/agent/fakeAgent.js +11 -7
  6. package/dist/agent/piAdapter.d.ts +2 -1
  7. package/dist/agent/piAdapter.js +28 -25
  8. package/dist/agent/port.d.ts +6 -1
  9. package/dist/agent/port.js +3 -0
  10. package/dist/cli/artifactCommand.d.ts +10 -0
  11. package/dist/cli/artifactCommand.js +120 -0
  12. package/dist/cli/envelopeCommand.d.ts +32 -0
  13. package/dist/cli/envelopeCommand.js +285 -0
  14. package/dist/cli/exportRunCommand.d.ts +10 -0
  15. package/dist/cli/exportRunCommand.js +150 -0
  16. package/dist/cli/handoffFormat.d.ts +22 -0
  17. package/dist/cli/handoffFormat.js +61 -0
  18. package/dist/cli/runCommand.d.ts +5 -1
  19. package/dist/cli/runCommand.js +36 -5
  20. package/dist/cli/runOutput.d.ts +4 -1
  21. package/dist/cli/runOutput.js +36 -8
  22. package/dist/cli/runsCommand.d.ts +22 -0
  23. package/dist/cli/runsCommand.js +591 -0
  24. package/dist/cli/skillsCommand.d.ts +16 -0
  25. package/dist/cli/skillsCommand.js +459 -0
  26. package/dist/cli/validateOutput.d.ts +2 -2
  27. package/dist/cli/validateOutput.js +2 -2
  28. package/dist/cli.js +110 -5
  29. package/dist/config/loadPipeline.js +5 -1
  30. package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
  31. package/dist/config/normalizePipelineStageEntry.js +25 -1
  32. package/dist/config/pipelineStageKeys.js +1 -1
  33. package/dist/config/resolveForkEmitContext.d.ts +2 -1
  34. package/dist/config/resolveForkEmitContext.js +23 -1
  35. package/dist/config/resolvePipelineDag.js +36 -4
  36. package/dist/envelope/check.d.ts +2 -0
  37. package/dist/envelope/check.js +51 -0
  38. package/dist/envelope/cloneForks.d.ts +2 -0
  39. package/dist/envelope/cloneForks.js +62 -0
  40. package/dist/envelope/forkChoice.js +1 -1
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.js +3 -1
  43. package/dist/mcp/catalogTools.d.ts +3 -0
  44. package/dist/mcp/catalogTools.js +187 -0
  45. package/dist/mcp/controlTools.d.ts +3 -0
  46. package/dist/mcp/controlTools.js +147 -0
  47. package/dist/mcp/deps.d.ts +11 -0
  48. package/dist/mcp/deps.js +1 -0
  49. package/dist/mcp/projectRun.d.ts +1 -40
  50. package/dist/mcp/projectRun.js +1 -64
  51. package/dist/mcp/resources.d.ts +5 -0
  52. package/dist/mcp/resources.js +45 -0
  53. package/dist/mcp/server.d.ts +16 -3
  54. package/dist/mcp/server.js +159 -3
  55. package/dist/mcp/toolResults.d.ts +7 -0
  56. package/dist/mcp/toolResults.js +6 -0
  57. package/dist/mcp/tools.d.ts +2 -7
  58. package/dist/mcp/tools.js +52 -103
  59. package/dist/mcp/waitRun.d.ts +46 -0
  60. package/dist/mcp/waitRun.js +146 -0
  61. package/dist/mcp/waitingGates.d.ts +12 -0
  62. package/dist/mcp/waitingGates.js +83 -0
  63. package/dist/package-meta.d.ts +1 -0
  64. package/dist/package-meta.js +1 -0
  65. package/dist/projection/projectRun.d.ts +44 -0
  66. package/dist/projection/projectRun.js +71 -0
  67. package/dist/prompt/priorEnvelope.d.ts +1 -1
  68. package/dist/prompt/priorEnvelope.js +4 -1
  69. package/dist/runstore/paths.js +2 -0
  70. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  71. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  72. package/dist/runstore/port.d.ts +13 -2
  73. package/dist/runstore/port.js +4 -1
  74. package/dist/runstore/runProjection.d.ts +1 -1
  75. package/dist/runstore/runProjection.js +14 -3
  76. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  77. package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
  78. package/dist/runstore/stageInstanceId.d.ts +4 -0
  79. package/dist/runstore/stageInstanceId.js +23 -0
  80. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  81. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  82. package/dist/runstore/trackProjection.js +28 -12
  83. package/dist/runstore/workspaceLayout.d.ts +1 -0
  84. package/dist/runstore/workspaceLayout.js +1 -1
  85. package/dist/runtime/cloneSchedule.d.ts +18 -0
  86. package/dist/runtime/cloneSchedule.js +233 -0
  87. package/dist/runtime/envelopeRouting.d.ts +2 -1
  88. package/dist/runtime/envelopeRouting.js +80 -13
  89. package/dist/runtime/pipelineScheduler.d.ts +6 -2
  90. package/dist/runtime/pipelineScheduler.js +166 -30
  91. package/dist/runtime/resumeReconstruct.js +8 -3
  92. package/dist/runtime/runChangeBus.d.ts +18 -0
  93. package/dist/runtime/runChangeBus.js +83 -0
  94. package/dist/runtime/runManager.d.ts +7 -0
  95. package/dist/runtime/runManager.js +31 -2
  96. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  97. package/dist/runtime/runRetryCoordinator.js +4 -1
  98. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  99. package/dist/runtime/stageAttemptBootstrap.js +17 -9
  100. package/dist/runtime/stageRunner.d.ts +1 -0
  101. package/dist/runtime/stageRunner.js +17 -15
  102. package/dist/runtime/stageWorker.js +11 -4
  103. package/dist/server/bootstrap.d.ts +32 -0
  104. package/dist/server/bootstrap.js +56 -0
  105. package/dist/server/createHttpHost.d.ts +34 -0
  106. package/dist/server/createHttpHost.js +70 -0
  107. package/dist/server/http.d.ts +6 -13
  108. package/dist/server/http.js +295 -374
  109. package/dist/server/mcpHost.d.ts +22 -0
  110. package/dist/server/mcpHost.js +21 -0
  111. package/dist/server/operatorResults.d.ts +13 -0
  112. package/dist/server/operatorResults.js +22 -0
  113. package/dist/tools/emitStageEnvelope.d.ts +11 -2
  114. package/dist/tools/emitStageEnvelope.js +22 -6
  115. package/dist/types/envelope.d.ts +2 -0
  116. package/dist/types/forkChoice.d.ts +22 -0
  117. package/dist/types/pipeline.d.ts +8 -0
  118. package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
  119. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  120. package/dist/ui/index.html +2 -2
  121. package/package.json +2 -1
  122. package/skills/install-suite.sh +107 -0
  123. package/skills/stageflow/SKILL.md +26 -0
  124. package/skills/stageflow/references/control-surface.md +28 -0
  125. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  126. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  127. package/skills/stageflow-author/SKILL.md +58 -0
  128. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  129. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  130. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  131. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  132. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  133. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  134. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  135. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  136. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  137. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  138. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  139. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  140. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  141. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  142. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  143. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  144. package/skills/stageflow-delegate/SKILL.md +18 -0
  145. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  146. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  147. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  148. package/skills/stageflow-run/SKILL.md +182 -0
  149. package/skills/stageflow-run/references/mcp-call.md +17 -0
  150. package/skills/stageflow-run/references/native-question-ui.md +78 -0
  151. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  152. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  153. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  154. package/skills/stageflow-session-capture/SKILL.md +94 -0
  155. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  156. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  157. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  158. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  159. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  160. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  161. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  162. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  163. package/skills/stageflow-setup/SKILL.md +110 -0
  164. package/dist/ui/assets/index-CFSzDZje.js +0 -118
@@ -0,0 +1,29 @@
1
+ # Catalog write conventions
2
+
3
+ Write pipeline and stage YAML with the native Write tool. `sf ui` does not need to be running. Do not call `createPipeline` or `createStage`.
4
+
5
+ ## Location
6
+
7
+ Project root is the git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory.
8
+
9
+ 1. **Manifest present.** Read `stageflow.yaml` at that root. Write the colocated set under a declared `catalog.pipelines` root. A new subdirectory under that root is enough — catalog scan is recursive.
10
+ 2. **No manifest.** Write the colocated set as a flat layout at the project root (pipeline file and stage files beside each other).
11
+
12
+ Do not write a `*.task.yaml`.
13
+
14
+ ## File shape
15
+
16
+ One directory (or the project root) holds:
17
+
18
+ | File | Required fields |
19
+ |---|---|
20
+ | `<pipeline-id>.pipeline.yaml` | `id` matching the filename stem; `stages:` object entries |
21
+ | `<stage-id>.yaml` | `id`, `system_prompt`, `model`; `gate_kinds` when the step is gated |
22
+
23
+ Each pipeline stage entry has `id` and `uses: ./<id>.yaml` (path relative to the pipeline file). Non-root stages add `needs: <parent-id>`. A deciding stage adds `fork:`. Filename stem matches `id` on every file.
24
+
25
+ ## Collisions
26
+
27
+ Before writing, list `*.pipeline.yaml` files in the target directory (and existing pipeline files under the same catalog root) plus every `*.yaml` / `*.yml` stage file beside the target pipeline. Read each file's top-level `id:` and its filename stem.
28
+
29
+ On a match for the candidate pipeline id or any stage id, ask the human for a different id. Write nothing until that id is free. Never replace an existing file silently. Proceed with a colliding name only when the human explicitly confirms overwrite.
@@ -0,0 +1,55 @@
1
+ # Stage prompt template
2
+
3
+ Every external stage file has `id`, `system_prompt`, and `model`. Filename stem matches `id`. Default `model` is `anthropic/claude-sonnet-4-5`. When the human names a different model, write that string verbatim.
4
+
5
+ ## Base
6
+
7
+ Use for every stage that is not a review point and does not decide a branch.
8
+
9
+ ```yaml
10
+ id: <id>
11
+ system_prompt: |
12
+ <one-line goal for this step>
13
+
14
+ Write any output this step produces via write_stage_artifact.
15
+
16
+ When finished, call emit_stage_envelope exactly once with status, summary,
17
+ artifacts, and a payload the next stage can use.
18
+ model: anthropic/claude-sonnet-4-5
19
+ ```
20
+
21
+ The prompt has no `ask_operator` line. It ends on the `emit_stage_envelope` instruction. Call `emit_stage_envelope` once per attempt.
22
+
23
+ ## Gated
24
+
25
+ When the step is a review, approval, or sign-off, add `gate_kinds` and resolve the gate before emit. Match this sequence: `write_stage_artifact` → `ask_operator` → `emit_stage_envelope` on accept.
26
+
27
+ ```yaml
28
+ id: <id>
29
+ gate_kinds:
30
+ - artifact_backed
31
+ system_prompt: |
32
+ <one-line goal>. Get operator acceptance before completing this stage.
33
+
34
+ 1. Write the reviewable output via write_stage_artifact.
35
+ 2. Call ask_operator with kind artifact_backed referencing that artifact path.
36
+ 3. On reject or change text: revise the artifact and call ask_operator again
37
+ with artifact_backed in this same stage. Do not complete yet.
38
+ 4. Call emit_stage_envelope with an advancing success status only after the
39
+ operator accepts. Never emit before accept.
40
+ model: anthropic/claude-sonnet-4-5
41
+ ```
42
+
43
+ Use the `gate_kinds` value that matches the human's review: `artifact_backed` for a file to accept, `confirm` for yes/no, `free_text` for an open reply, `multi_question` for a batch. `ask_operator` does not complete the stage.
44
+
45
+ ## Fork
46
+
47
+ When the pipeline entry has `fork`, the success emit names immediate successors in `fork_choice`. Add this block to the base (or gated) prompt:
48
+
49
+ ```
50
+ On a success emit, include fork_choice naming immediate successor id(s) that
51
+ should run. Name only ids listed as this stage's children. select: one →
52
+ exactly one id. select: subset → one or more of those ids.
53
+ ```
54
+
55
+ `fork_choice` on a failure emit is ignored. Leave `clone_forks` unset.
@@ -0,0 +1,38 @@
1
+ # Validate and report
2
+
3
+ After the pipeline and stage files exist, validate them before telling the human authoring is complete.
4
+
5
+ ## Command
6
+
7
+ Default — always available:
8
+
9
+ ```
10
+ sf validate --pipeline <written-path> --strict
11
+ ```
12
+
13
+ MCP `validate` is allowed only when [`../../stageflow/scripts/detect-host.mjs`](../../stageflow/scripts/detect-host.mjs) reports a host up. From this skill directory:
14
+
15
+ ```
16
+ node ../stageflow/scripts/detect-host.mjs
17
+ ```
18
+
19
+ | stdout | next |
20
+ |---|---|
21
+ | `up <baseUrl>` | MCP `validate` with `{ "pipeline": "<written-path>", "strict": true }` is allowed |
22
+ | `down <baseUrl>` | stay on the CLI command |
23
+
24
+ Do not write a second probe. MCP-vs-CLI policy: [`../../stageflow/references/control-surface.md`](../../stageflow/references/control-surface.md). Both paths return the same `ValidationResult` shape (`ok`, `summary`, `findings[]`).
25
+
26
+ ## Failure
27
+
28
+ When `ok` is false or the CLI exits non-zero, print each finding (severity, code, path, message). Fix the offending pipeline or stage file. Re-validate. Repeat until `ok` is true. Never report authoring complete on a failing validate.
29
+
30
+ ## Success
31
+
32
+ When validate exits 0 / `ok: true`, print:
33
+
34
+ 1. Pipeline id and pipeline path.
35
+ 2. Every stage path written in this invocation.
36
+ 3. Name `stageflow-run` if the human wants to execute the pipeline.
37
+
38
+ Do not write a `*.task.yaml`. Do not start a run here.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: stageflow-delegate
3
+ description: Looks up a catalog pipeline for a repeating request, then reuses it or codifies it and runs. Use when this skill is opened for a repeating-pattern request.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Stageflow delegate
8
+
9
+ Look up the catalog once, then branch. A match runs; no match authors, then runs.
10
+
11
+ Talking jobs cite [`../stageflow/references/control-surface.md`](../stageflow/references/control-surface.md). This job names `stageflow-run` for execution and does not choose MCP vs CLI.
12
+
13
+ 1. Look up the requested pattern. Follow [`references/pattern-detection.md`](references/pattern-detection.md) until you have a match, a disambiguation, or no match.
14
+ 2. Branch on that result. Follow [`references/authoring-or-run.md`](references/authoring-or-run.md). Name `stageflow-run` and stop.
15
+
16
+ Act only when this skill is the one opened. Leave the harness's builtin subagents in place; never suggest disabling, replacing, or bypassing them.
17
+
18
+ Worked pair: [`references/example-walkthrough.md`](references/example-walkthrough.md).
@@ -0,0 +1,15 @@
1
+ # Authoring or run
2
+
3
+ Take the lookup band. Open the named job and follow it. Do not start a run, write a throwaway task, or answer a gate from this skill.
4
+
5
+ | band | open | then |
6
+ |---|---|---|
7
+ | match | [`stageflow-run`](../../stageflow-run/SKILL.md) for that pipeline | stop |
8
+ | no match, and this session already did the matching work with readable history (this chat, or a path / paste the human supplied) | [`stageflow-session-capture`](../../stageflow-session-capture/SKILL.md) | when it reports a pipeline path, open [`stageflow-run`](../../stageflow-run/SKILL.md) for that path and stop |
9
+ | no match, and no readable history | [`stageflow-author`](../../stageflow-author/SKILL.md) | when it reports a pipeline path, open [`stageflow-run`](../../stageflow-run/SKILL.md) for that path and stop |
10
+
11
+ History is a coarse yes/no. Unsure → treat as no history.
12
+
13
+ If the authoring job stops on a blocker (it names [`stageflow-setup`](../../stageflow-setup/SKILL.md), or prints `blocked: <reason>`), print that blocker and stop. Do not retry, log in, or work around it.
14
+
15
+ **Done when** `stageflow-run` has been named, or an authoring blocker has been printed.
@@ -0,0 +1,30 @@
1
+ # Example walkthrough
2
+
3
+ Content walkthrough. No live harness session is required.
4
+
5
+ Scratch catalog at first sight:
6
+
7
+ | path | id | task `goal` (if readable) |
8
+ |---|---|---|
9
+ | `pipelines/hello.pipeline.yaml` | `hello` | Draft a one-page brief |
10
+ | `pipelines/plan-review.pipeline.yaml` | `plan-review` | Prove a review loop |
11
+
12
+ ## First sight
13
+
14
+ Request: "Every week I gather sources, summarize them, and send a digest. Make that a reusable pipeline and run it."
15
+
16
+ [pattern-detection](pattern-detection.md) tokens include `gather`, `sources`, `summarize`, `send`, `digest`. Neither `hello` nor `plan-review` shares a distinctive topic token. Band: **no match**. This chat has not already done that work. [authoring-or-run](authoring-or-run.md) opens `stageflow-author`, then names `stageflow-run` on the written pipeline (id `research-digest`, path `pipelines/research-digest.pipeline.yaml`). One pipeline is authored. None is invented a second time.
17
+
18
+ If this session had already gathered, summarized, and sent — with that history still in chat or a pointer the human supplied — the same no-match would open `stageflow-session-capture` instead, then `stageflow-run`.
19
+
20
+ ## Second sight
21
+
22
+ Same catalog, plus `pipelines/research-digest.pipeline.yaml` (`id: research-digest`).
23
+
24
+ Request: "Run the weekly research digest."
25
+
26
+ Tokens include `research`, `digest`. Exact topic overlap on one candidate's kebab `id`. Band: **match**. [authoring-or-run](authoring-or-run.md) opens `stageflow-run` only. No second pipeline is written.
27
+
28
+ ## Ordinary request
29
+
30
+ Request: "Fix the typo in README." This skill is not opened. The harness's builtin subagent does the edit. Builtin subagents stay available.
@@ -0,0 +1,44 @@
1
+ # Pattern detection
2
+
3
+ Decide match vs first-sight from the catalog. Listings carry `path`, `id`, and `stages` — no `description` or `title`. Do not invent a list command.
4
+
5
+ ## Roots
6
+
7
+ Project root is the git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory. Read `stageflow.yaml` there. Walk each `catalog.pipelines` root. Use `catalog.patterns.pipeline` (default `*.pipeline.yaml`). Skip a path whose repo-relative prefix is in `catalog.exclude`.
8
+
9
+ Manifest missing or unreadable, a listed root missing, or zero pipeline files → **no match**. Not an error.
10
+
11
+ **Done when** you have a candidate list, or a no-match.
12
+
13
+ ## Host
14
+
15
+ From this skill directory:
16
+
17
+ ```
18
+ node ../stageflow/scripts/detect-host.mjs
19
+ ```
20
+
21
+ Script: [`../../stageflow/scripts/detect-host.mjs`](../../stageflow/scripts/detect-host.mjs). Stdout is `up <baseUrl>` or `down <baseUrl>`.
22
+
23
+ | stdout | catalog |
24
+ |---|---|
25
+ | `up <baseUrl>` | MCP `list_pipelines`, then `describe_pipeline` on a shortlisted path |
26
+ | `down <baseUrl>` | Read `*.pipeline.yaml` under the roots. When a paired `*.task.yaml` is readable, take its `goal` |
27
+
28
+ A paired task is a `*.task.yaml` in the same directory as the pipeline, or one whose `id` / directory words overlap that pipeline. Skip `goal` when no such file is readable. Host-up listings do not include `goal`.
29
+
30
+ **Done when** each candidate has `path`, `id`, and (when readable) a task `goal`.
31
+
32
+ ## Score
33
+
34
+ Split the request, each kebab `id`, and each directory segment under the catalog root into lowercase tokens. Drop empty tokens and generic words (`pipeline`, `stage`, `task`, `run`, `the`, `a`, `an`). Add tokens from a readable task `goal`.
35
+
36
+ | band | test | next |
37
+ |---|---|---|
38
+ | match | The request names one candidate's kebab `id`, or exactly one candidate has a strong overlap (a distinctive topic token shared with `id`, directory words, or `goal`) | reuse that pipeline |
39
+ | ask once | Two or more candidates are plausible | print `id` + `path` for each; ask which; that answer is the match |
40
+ | no match | Zero candidates, or the catalog was empty / unreadable | first-sight |
41
+
42
+ Exact `id` beats keyword overlap. Do not guess among two-plus. Do not treat a weak shared word (`review`, `send`, `weekly`) as a match when more than one candidate could claim it.
43
+
44
+ **Done when** the band is match, a human-chosen match, or no match.
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: stageflow-run
3
+ description: >-
4
+ Starts a catalog pipeline from the harness, reports progress, and answers
5
+ every HITL gate on the host native question UI when it can, otherwise in
6
+ this chat. Triggers: run a pipeline, start <pipeline>, check on my run,
7
+ answer the pending question.
8
+ compatibility: Requires the sf CLI on PATH. An MCP host (sf ui or sf mcp) is optional.
9
+ disable-model-invocation: true
10
+ ---
11
+
12
+ # Stageflow run
13
+
14
+ Start a catalog pipeline, present HITL on the host native question UI when it can, and report the outcome. Author and session-capture own catalog YAML; this job owns the run and any throwaway `*.task.yaml`.
15
+
16
+ Talking jobs cite [`../stageflow/references/control-surface.md`](../stageflow/references/control-surface.md). Probe with [`../stageflow/scripts/detect-host.mjs`](../stageflow/scripts/detect-host.mjs) only. Do not write a second probe.
17
+
18
+ MCP tool shapes: [`docs/mcp.md`](../../docs/mcp.md). CLI flags and exit codes: [`docs/cli-reference.md`](../../docs/cli-reference.md). Direct tool calls: [`references/mcp-call.md`](references/mcp-call.md). Selection: [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md). Gate presentation: [`references/native-question-ui.md`](references/native-question-ui.md).
19
+
20
+ ## Preconditions
21
+
22
+ Run `sf --version`. **Done when** it prints a version. If `sf` is missing, stop and name `stageflow-setup`.
23
+
24
+ ## Probe
25
+
26
+ ```
27
+ node ../stageflow/scripts/detect-host.mjs
28
+ node ../stageflow/scripts/detect-host.mjs --base-url http://127.0.0.1:3847
29
+ ```
30
+
31
+ | stdout | path |
32
+ |---|---|
33
+ | `up <baseUrl>` | [MCP path](#mcp-path) |
34
+ | `down <baseUrl>` | [CLI path](#cli-path) |
35
+
36
+ **Done when** you have `up` or `down` and the base URL.
37
+
38
+ ## Select
39
+
40
+ Read [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md). Follow it until you have a pipeline filesystem path and a task (catalog path, MCP inline object, or CLI throwaway file).
41
+
42
+ If this chat already has a `runId` and the request is check, answer, or continue: skip start. Host up → [Wait](#wait). Host down → [CLI wait](#cli-wait).
43
+
44
+ **Done when** the target is named, or a live `runId` is in hand.
45
+
46
+ ## MCP tools
47
+
48
+ Prefer this harness's native Stageflow MCP tools when their names are already in the tool list. When they are not, call [`scripts/mcp-call.mjs`](scripts/mcp-call.mjs) — see [`references/mcp-call.md`](references/mcp-call.md). Use `--stateless` only when the **user** started the host with `--mcp-stateless`. Do not start `sf mcp` from this skill.
49
+
50
+ Call only these Stageflow MCP tools: `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `list_waiting`, `answer_gate`, `get_health`. Host question tools already in this harness's tool list (`AskQuestion`, `AskUserQuestion`, `ask_user`) are for [Gate](#gate) presentation, not Stageflow MCP.
51
+
52
+ ## MCP path
53
+
54
+ ### Start
55
+
56
+ `start_run` with `pipeline` plus exactly one of `task_path` or inline `task`.
57
+
58
+ | result | next |
59
+ |---|---|
60
+ | `{ "runId" }` | [Wait](#wait) |
61
+ | `busy_capacity` / `busy_checkout` | [Report](#report) the included fields. Stop. Do not retry. |
62
+ | other `isError` | [Report](#report) the payload. Stop. |
63
+
64
+ **Done when** you have a `runId`, or a busy/error report is printed.
65
+
66
+ ### Wait
67
+
68
+ `wait_run` with `{ "runId", "until": "any" }`. A shorter `timeout_ms` is fine when the harness tool timeout is tight.
69
+
70
+ | `reason` | next |
71
+ |---|---|
72
+ | `waiting` or `already` with a waiting snapshot | [Gate](#gate) |
73
+ | `terminal` | [Report](#report) |
74
+ | `timeout` | call `wait_run` again |
75
+ | `isError` with `code: "aborted"` | [Report](#report): the run continues, resumable later. Stop. |
76
+ | other `isError` | [Report](#report) the payload. Stop. |
77
+
78
+ **Done when** the run is waiting (hand to Gate), terminal, aborted, or a hard error is printed.
79
+
80
+ ### Gate
81
+
82
+ Read [`references/native-question-ui.md`](references/native-question-ui.md) before presenting a pending prompt.
83
+
84
+ 1. `list_waiting` with `{ "runId" }`.
85
+ 2. Print the pending prompt text **verbatim** (and artifacts / sub-questions when present).
86
+ 3. Present the decision as that reference directs:
87
+ - If a host question tool is already in this harness's tool list and the gate is representable, invoke that picker. For `multi_question`, one picker call with one question per sub-item when **every** sub-question is representable, then one `answer_gate`; otherwise the **whole** gate in this chat.
88
+ - Otherwise collect the reply in this chat.
89
+ 4. Map the reply to the prompt `kind` (picker Accept/Reject → `accept`/`reject`):
90
+
91
+ | kind | `answer` |
92
+ |---|---|
93
+ | `free_text` | `{ "promptId", "kind": "free_text", "text" }` |
94
+ | `confirm` | `{ "promptId", "kind": "confirm", "decision": "accept" \| "reject" }` |
95
+ | `artifact_backed` | `{ "promptId", "kind": "artifact_backed", "decision": "accept" \| "reject" }` |
96
+ | `multi_question` | `{ "promptId", "kind": "multi_question", "answers": { "<id>": { "kind", "text" \| "decision" } } }` |
97
+
98
+ `promptId` is `pending_prompt.id` or `waiting_prompt_id`. Map yes/y/accept/approve/Accept to `accept`; no/n/reject/deny/Reject to `reject`. Each `multi_question` sub-answer keeps that sub's `kind` (`confirm` → `decision`, `free_text` → `text`). Collect every sub-answer before `answer_gate`.
99
+
100
+ 5. `answer_gate` with `{ "runId", "stageId", "answer" }`.
101
+ 6. Return to [Wait](#wait).
102
+
103
+ **Done when** `answer_gate` returns `{ "ok": true }` and Wait is re-entered. On `isError` (400 / 404 / 409), print the payload and collect a corrected reply the same way (picker if still representable; otherwise this chat).
104
+
105
+ ## CLI path
106
+
107
+ Discovery without a host: read `stageflow.yaml` catalog roots as in [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md).
108
+
109
+ ```
110
+ sf run --task <path> --pipeline <path> --json
111
+ ```
112
+
113
+ Add `--checkout`, `--git-sha`, `--ci-pr-url`, `--ci-job-url`, `--operator-cwd`, or `--operator-agent-dir` only when the human supplied them. Always pass `--json`. Omit `--include stages` unless the human asked for a stage projection — and then only together with `--json`.
114
+
115
+ Parse the single JSON document.
116
+
117
+ | exit | `outcome` | next |
118
+ |---|---|
119
+ | `0` | `succeeded` | [Report](#report) |
120
+ | `1` | `failed` | [Report](#report) `reason` verbatim |
121
+ | `1` | `busy` | [Report](#report) `busy_capacity` / `busy_checkout` and the included fields. Stop. |
122
+ | `2` | `waiting` | [CLI wait](#cli-wait) with this `runId` / `runDir` |
123
+
124
+ **Done when** the outcome is reported, or a waiting exit has handed `runId` to CLI wait.
125
+
126
+ ## CLI wait
127
+
128
+ Host down after `sf run` exit `2`, or when this chat already has a `runId` and the host is down. Do not start `sf mcp`. Presentation follows [`references/native-question-ui.md`](references/native-question-ui.md). Submit with `sf runs answer --json`.
129
+
130
+ 1. Probe again with [`../stageflow/scripts/detect-host.mjs`](../stageflow/scripts/detect-host.mjs) (the host may have appeared). Probe before each `sf runs answer`.
131
+ 2. **Up:** use that host — native tools if present, otherwise `mcp-call.mjs` (omit `--stateless` unless the user started the host with `--mcp-stateless`). Continue at [Gate](#gate).
132
+ 3. **Down:**
133
+
134
+ ```
135
+ sf runs waiting --run <runId> --json
136
+ ```
137
+
138
+ Print the pending prompt **verbatim**. Present it as native-question-ui directs (picker or chat). Map the reply with the [Gate](#gate) kind table.
139
+
140
+ ```
141
+ sf runs answer --run <runId> --stage <stageId> --answer '<json>' --json
142
+ ```
143
+
144
+ Success is `{ "ok": true }` exit `0` even if the run parks again. Do not treat answer as terminal.
145
+
146
+ ```
147
+ sf runs wait --run <runId> --json --until any
148
+ ```
149
+
150
+ A shorter `--timeout-ms` is fine when the harness timeout is tight. Default `60000`, max `240000`. Branch on JSON `reason`, not wait exit `0`:
151
+
152
+ | `reason` | next |
153
+ |---|---|
154
+ | `waiting` or `already` with a waiting snapshot | `sf runs waiting` then answer |
155
+ | `terminal` | [Report](#report) |
156
+ | `timeout` | call `sf runs wait` again |
157
+
158
+ Wait abort (exit `130`, `{ "error", "code": "aborted" }`) → [Report](#report): the run continues, resumable later. Stop. Do not reuse `sf run` exit `2` for wait.
159
+
160
+ 4. If `sf runs answer` refuses because the host came up, continue that gate via MCP [Gate](#gate). Do not dual-write.
161
+
162
+ If this chat ends before terminal, say the run stays in `.stageflow/` and to continue by re-invoking this skill (or starting a host and using MCP).
163
+
164
+ **Done when** the run is terminal, aborted, or a hard error is printed.
165
+
166
+ ## Report
167
+
168
+ Print one shape on every path:
169
+
170
+ 1. Current or last-known stage (MCP live snapshot, or "start/finish only" on a CLI-only stretch).
171
+ 2. Waiting prompt text verbatim when the run is parked.
172
+ 3. Outcome: `succeeded`, `failed`, `waiting`, or `busy`.
173
+ 4. Run id.
174
+ 5. Run folder (`runDir` / `.stageflow/`).
175
+
176
+ MCP `get_run` / `wait_run` and `sf runs wait` / `sf runs show` `--json` carry live stage state. `sf run --json` reports start and finish only — say that in chat when the CLI path ran without a later wait/show. A succeeded report names id and folder and does not keep waiting language. A failure names `reason` in this same shape.
177
+
178
+ **Done when** that five-part report is printed.
179
+
180
+ ## Non-goals
181
+
182
+ This job starts, watches, and answers runs. It does not require `sf ui`. The operator console is not the answer path. It does not register or invent an MCP tool. It does not start `sf mcp`. It does not retry, abandon, or rerun stages.
@@ -0,0 +1,17 @@
1
+ # mcp-call
2
+
3
+ Use [`../scripts/mcp-call.mjs`](../scripts/mcp-call.mjs) when the Stageflow host is up and this harness has no native Stageflow MCP tools. Do not start `sf mcp` from this skill.
4
+
5
+ ```
6
+ node scripts/mcp-call.mjs --base-url <url> --tool <name> --args '<json>' [--stateless]
7
+ ```
8
+
9
+ Default `--base-url` is `http://127.0.0.1:3847`. `--args` defaults to `{}`.
10
+
11
+ `--stateless` sends one `tools/call` and no session header. Use it for hosts the **user** started with `sf mcp --mcp-stateless`, not a skill-started process.
12
+
13
+ Without `--stateless`: `initialize`, capture `Mcp-Session-Id`, reuse it for `tools/call`.
14
+
15
+ Stdout is the tool JSON. Exit `0` on success, including `wait_run` with `reason: "timeout"` (keep polling). Exit `1` on transport failure or `isError: true`. Exit `2` on usage or malformed `--args` — that parse error happens before any network call.
16
+
17
+ Allowed `--tool` names (see [`docs/mcp.md`](../../../docs/mcp.md)): `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `list_waiting`, `answer_gate`, `get_health`.
@@ -0,0 +1,78 @@
1
+ # Native question UI
2
+
3
+ Present a waiting HITL gate on the coding-agent host's structured question tool when that tool can represent the gate. Otherwise collect the reply in this chat. Submit with `answer_gate` when the Stageflow host is up, or `sf runs answer --json` when it is down. This file is presentation only — it does not change MCP tools, `detect-host.mjs`, or the operator console.
4
+
5
+ Read `pending_prompt` from `list_waiting` or `sf runs waiting --json` (not only `waiting_questions` strings).
6
+
7
+ ## Detect a picker
8
+
9
+ Inspect **this harness's current tool list**. A picker is a tool that collects a structured choice (question text plus labeled options), not a free-form chat reply.
10
+
11
+ Do not run `detect-host.mjs` for this. That script is Stageflow host up/down (MCP vs CLI). It does not detect a question UI.
12
+
13
+ | If this name is in the tool list | Use it |
14
+ |---|---|
15
+ | `AskQuestion` | Cursor question cards |
16
+ | `AskUserQuestion` | Claude Code questions |
17
+ | `ask_user` | Pi questions |
18
+
19
+ If none of those (or another options-based question tool already in the list) is present, collect in chat. Do not invent a tool name or call shape. Codex and OpenCode stay in chat until an options-based question tool appears in the list.
20
+
21
+ Pass the pending message as the question text and the mapped labels as that tool's options. One question per call except `multi_question`: one call with one question per representable sub-item when the tool accepts a questions array (Cursor `AskQuestion` does). Do not invent placeholder options such as Type in Other — host Other is the custom-text escape. If the picker returns a custom or Other value, map that text the same as a chat reply.
22
+
23
+ **Done when** you know picker or chat for this gate.
24
+
25
+ ## Representable vs chat
26
+
27
+ | Gate | Picker | Chat |
28
+ |---|---|---|
29
+ | `confirm` | Accept and Reject | Host has no picker |
30
+ | `artifact_backed` | Accept and Reject (decision only) | Host has no picker |
31
+ | `free_text` with a harvested closed set | Those names as options | Open-ended, or harvest is ambiguous |
32
+ | `free_text` with no closed set | — | Always |
33
+ | `multi_question` | One picker call with one question per sub-item when **every** sub-question is representable; then one submit | Any sub-question is open or unmappable — the **whole** gate in chat |
34
+
35
+ Map picker Accept / yes / approve → `accept`. Reject / no / deny → `reject`. Always include `promptId` (`pending_prompt.id` or `waiting_prompt_id`).
36
+
37
+ | Item | Submit |
38
+ |---|---|
39
+ | Top-level or sub `confirm` | `{ "kind": "confirm", "decision": "accept" \| "reject" }` |
40
+ | Top-level `artifact_backed` | `{ "kind": "artifact_backed", "decision": "accept" \| "reject" }` plus optional `text` after a reject follow-up |
41
+ | Top-level or sub `free_text` (including harvested) | `{ "kind": "free_text", "text": "<selected label>" }` |
42
+
43
+ Never submit a `confirm` item as `free_text`. For `multi_question`, wrap sub-answers in the Gate `answers` object and submit once (`answer_gate` or `sf runs answer --json`).
44
+
45
+ When in doubt, chat.
46
+
47
+ **Done when** the path (picker or chat) is chosen from this table.
48
+
49
+ ## Harvest
50
+
51
+ Harvest only from `pending_prompt.message` (or a `multi_question` item `message`) when it names a **hard closed set**:
52
+
53
+ - `reply with exactly one of:`
54
+ - `choose one of:`
55
+ - `exactly one of`
56
+
57
+ Split the names after that directive (commas, `or`, newlines). Those literal names are the picker options.
58
+
59
+ Do **not** harvest loose example lists (`you might try`, `e.g.`, `for example` without `exactly one of`). A listed example set that is not a hard closed directive stays in chat.
60
+
61
+ Submit the selected label as `text` for `free_text` items. If the prompt also lists aliases for the same choice (for example `minimal` and `prototype~1`), keep every listed name on the card; do not rewrite the label at submit — the stage maps synonyms.
62
+
63
+ **Done when** the option list is the prompt's literal closed set, or the gate stayed in chat.
64
+
65
+ ## Artifact-backed
66
+
67
+ 1. Print the pending message and artifact paths (from `list_waiting`) in this chat. Do not put artifact bodies on the card. Do not call `read_artifact` unless the human asked to open a file.
68
+ 2. Open the picker with Accept and Reject.
69
+ 3. Accept → submit immediately (`decision: "accept"`).
70
+ 4. Reject → one chat follow-up for optional notes, then submit `decision: "reject"` and `text` when they typed notes.
71
+
72
+ **Done when** the operator has seen the paths in chat and the decision is on the picker (or chat if there is no picker).
73
+
74
+ ## Errors
75
+
76
+ On MCP `answer_gate` `isError` (400 / 404 / 409) or CLI `sf runs answer` exit `1`, print the payload. Collect a corrected reply on the picker if the gate is still representable; otherwise chat. Then submit again.
77
+
78
+ **Done when** `{ "ok": true }` or the error payload has been printed and a corrected reply is in hand.
@@ -0,0 +1,52 @@
1
+ # Task and pipeline selection
2
+
3
+ Resolve one pipeline filesystem path and one task before `start_run` or `sf run`. Ask a disambiguating question on more than one match. Do not guess.
4
+
5
+ ## Named
6
+
7
+ If the human named a pipeline or task, match that string against catalog `id` and `path`. One match → use it. Several → print id + path and ask which. Zero → say so and offer the catalog list.
8
+
9
+ ## Unnamed
10
+
11
+ Print a short pick list (id + path). Ask which.
12
+
13
+ ## MCP catalog
14
+
15
+ `list_pipelines` and `list_tasks` (native tools, or [`mcp-call.md`](mcp-call.md)). Listing objects carry `path` and `id`.
16
+
17
+ ## CLI catalog
18
+
19
+ There is no CLI list command. Read `stageflow.yaml` at the project root (git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory).
20
+
21
+ Walk each `catalog.pipelines` / `catalog.tasks` root. Match `catalog.patterns.pipeline` (default `*.pipeline.yaml`) and `catalog.patterns.task` (default `*.task.yaml`). Skip paths whose repo-relative prefix is in `catalog.exclude`.
22
+
23
+ ## Task input
24
+
25
+ ### MCP
26
+
27
+ An existing `task_path` from `list_tasks`, or an inline `task` object built from the stated goal:
28
+
29
+ ```json
30
+ { "id": "<slug>", "goal": "<goal>", "context": "optional", "constraints": "optional", "checkout": "optional" }
31
+ ```
32
+
33
+ `start_run` accepts that object. Do not write a scratch file on the MCP path.
34
+
35
+ ### CLI
36
+
37
+ `sf run` accepts `--task <path>` only. Reuse a catalog `*.task.yaml`, or write a throwaway file at the project root:
38
+
39
+ ```
40
+ .scratch/stageflow-run/<id>.task.yaml
41
+ ```
42
+
43
+ ```yaml
44
+ id: <slug>
45
+ goal: <human goal>
46
+ ```
47
+
48
+ Add `context`, `constraints`, or `checkout` only when the human gave them. Pass that path as `--task`.
49
+
50
+ ## Optional run flags
51
+
52
+ `--checkout`, `--git-sha`, `--ci-pr-url`, `--ci-job-url` only when the human supplied them.