stageflow 0.2.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +179 -14
  2. package/dist/agent/activity.d.ts +12 -1
  3. package/dist/agent/activity.js +37 -2
  4. package/dist/agent/activityObserver.d.ts +5 -1
  5. package/dist/agent/activityObserver.js +174 -11
  6. package/dist/agent/cursorProvider.d.ts +1 -10
  7. package/dist/agent/cursorProvider.js +18 -10
  8. package/dist/agent/fakeAgent.js +15 -7
  9. package/dist/agent/piAdapter.d.ts +18 -1
  10. package/dist/agent/piAdapter.js +115 -45
  11. package/dist/agent/port.d.ts +7 -0
  12. package/dist/agent/port.js +3 -0
  13. package/dist/agent/providerAuth.js +12 -7
  14. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  15. package/dist/catalog/displayCatalogPath.js +28 -0
  16. package/dist/cli/artifactCommand.d.ts +10 -0
  17. package/dist/cli/artifactCommand.js +120 -0
  18. package/dist/cli/envelopeCommand.d.ts +32 -0
  19. package/dist/cli/envelopeCommand.js +285 -0
  20. package/dist/cli/exportRunCommand.d.ts +10 -0
  21. package/dist/cli/exportRunCommand.js +150 -0
  22. package/dist/cli/handoffFormat.d.ts +22 -0
  23. package/dist/cli/handoffFormat.js +61 -0
  24. package/dist/cli/initCommand.d.ts +9 -0
  25. package/dist/cli/initCommand.js +71 -0
  26. package/dist/cli/initTemplates.d.ts +3 -0
  27. package/dist/cli/initTemplates.js +19 -0
  28. package/dist/cli/operatorCatalog.d.ts +10 -0
  29. package/dist/cli/operatorCatalog.js +16 -0
  30. package/dist/cli/runCommand.d.ts +7 -1
  31. package/dist/cli/runCommand.js +83 -11
  32. package/dist/cli/runOutput.d.ts +4 -1
  33. package/dist/cli/runOutput.js +36 -8
  34. package/dist/cli/skillsCommand.d.ts +16 -0
  35. package/dist/cli/skillsCommand.js +459 -0
  36. package/dist/cli/validateCommand.d.ts +1 -1
  37. package/dist/cli/validateCommand.js +20 -3
  38. package/dist/cli.d.ts +1 -0
  39. package/dist/cli.js +128 -22
  40. package/dist/config/browseCatalog.d.ts +45 -0
  41. package/dist/config/browseCatalog.js +170 -0
  42. package/dist/config/createPipeline.d.ts +12 -3
  43. package/dist/config/createPipeline.js +292 -100
  44. package/dist/config/createStage.d.ts +10 -4
  45. package/dist/config/createStage.js +44 -14
  46. package/dist/config/listConfig.d.ts +14 -19
  47. package/dist/config/listConfig.js +26 -191
  48. package/dist/config/listModelsFromManifest.d.ts +2 -0
  49. package/dist/config/listModelsFromManifest.js +4 -0
  50. package/dist/config/loadOutcome.d.ts +4 -0
  51. package/dist/config/loadPipeline.d.ts +1 -15
  52. package/dist/config/loadPipeline.js +88 -142
  53. package/dist/config/loadStage.d.ts +4 -0
  54. package/dist/config/loadStage.js +60 -32
  55. package/dist/config/loadStageflowManifest.d.ts +5 -0
  56. package/dist/config/loadStageflowManifest.js +157 -0
  57. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  58. package/dist/config/mergePipelineIncludes.js +141 -0
  59. package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
  60. package/dist/config/normalizePipelineStageEntry.js +210 -0
  61. package/dist/config/pipelineStageKeys.d.ts +4 -0
  62. package/dist/config/pipelineStageKeys.js +14 -0
  63. package/dist/config/resolveCatalogContext.d.ts +13 -0
  64. package/dist/config/resolveCatalogContext.js +13 -0
  65. package/dist/config/resolveForkEmitContext.d.ts +4 -0
  66. package/dist/config/resolveForkEmitContext.js +34 -0
  67. package/dist/config/resolvePipelineDag.d.ts +5 -1
  68. package/dist/config/resolvePipelineDag.js +86 -36
  69. package/dist/config/scanCatalogPaths.d.ts +4 -0
  70. package/dist/config/scanCatalogPaths.js +83 -0
  71. package/dist/config/validateCatalog.d.ts +22 -4
  72. package/dist/config/validateCatalog.js +169 -132
  73. package/dist/envelope/check.d.ts +2 -0
  74. package/dist/envelope/check.js +58 -0
  75. package/dist/envelope/cloneForks.d.ts +2 -0
  76. package/dist/envelope/cloneForks.js +62 -0
  77. package/dist/envelope/forkChoice.d.ts +3 -0
  78. package/dist/envelope/forkChoice.js +47 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/mcp/catalogTools.d.ts +3 -0
  82. package/dist/mcp/catalogTools.js +187 -0
  83. package/dist/mcp/controlTools.d.ts +3 -0
  84. package/dist/mcp/controlTools.js +147 -0
  85. package/dist/mcp/deps.d.ts +11 -0
  86. package/dist/mcp/deps.js +1 -0
  87. package/dist/mcp/projectRun.d.ts +1 -37
  88. package/dist/mcp/projectRun.js +1 -57
  89. package/dist/mcp/resources.d.ts +5 -0
  90. package/dist/mcp/resources.js +45 -0
  91. package/dist/mcp/server.d.ts +16 -3
  92. package/dist/mcp/server.js +159 -3
  93. package/dist/mcp/toolResults.d.ts +7 -0
  94. package/dist/mcp/toolResults.js +6 -0
  95. package/dist/mcp/tools.d.ts +2 -7
  96. package/dist/mcp/tools.js +52 -86
  97. package/dist/mcp/waitRun.d.ts +46 -0
  98. package/dist/mcp/waitRun.js +146 -0
  99. package/dist/mcp/waitingGates.d.ts +12 -0
  100. package/dist/mcp/waitingGates.js +83 -0
  101. package/dist/project/findProjectRoot.d.ts +2 -0
  102. package/dist/project/findProjectRoot.js +51 -0
  103. package/dist/project/globalHome.d.ts +2 -0
  104. package/dist/project/globalHome.js +19 -0
  105. package/dist/project/resolveProjectContext.d.ts +9 -0
  106. package/dist/project/resolveProjectContext.js +38 -0
  107. package/dist/project/resolveStageflowContext.d.ts +11 -0
  108. package/dist/project/resolveStageflowContext.js +66 -0
  109. package/dist/projection/projectRun.d.ts +44 -0
  110. package/dist/projection/projectRun.js +71 -0
  111. package/dist/prompt/priorEnvelope.d.ts +1 -1
  112. package/dist/prompt/priorEnvelope.js +4 -1
  113. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  114. package/dist/runstore/normalizeCatalogPath.js +4 -0
  115. package/dist/runstore/paths.js +2 -0
  116. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  117. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  118. package/dist/runstore/port.d.ts +22 -2
  119. package/dist/runstore/port.js +4 -1
  120. package/dist/runstore/runProjection.d.ts +1 -1
  121. package/dist/runstore/runProjection.js +25 -4
  122. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  123. package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
  124. package/dist/runstore/sqlite/schema.d.ts +1 -1
  125. package/dist/runstore/sqlite/schema.js +4 -1
  126. package/dist/runstore/stageInstanceId.d.ts +4 -0
  127. package/dist/runstore/stageInstanceId.js +23 -0
  128. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  129. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  130. package/dist/runstore/trackProjection.js +28 -12
  131. package/dist/runstore/workspaceLayout.d.ts +1 -0
  132. package/dist/runstore/workspaceLayout.js +1 -1
  133. package/dist/runtime/cloneSchedule.d.ts +18 -0
  134. package/dist/runtime/cloneSchedule.js +233 -0
  135. package/dist/runtime/credentialBinding.d.ts +8 -6
  136. package/dist/runtime/credentialBinding.js +18 -24
  137. package/dist/runtime/envelopeRouting.d.ts +2 -1
  138. package/dist/runtime/envelopeRouting.js +80 -13
  139. package/dist/runtime/pipelineRunner.d.ts +3 -0
  140. package/dist/runtime/pipelineRunner.js +15 -1
  141. package/dist/runtime/pipelineScheduler.d.ts +8 -2
  142. package/dist/runtime/pipelineScheduler.js +215 -39
  143. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  144. package/dist/runtime/reloadRunCatalog.js +44 -0
  145. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  146. package/dist/runtime/resumeReconstruct.js +31 -10
  147. package/dist/runtime/runChangeBus.d.ts +18 -0
  148. package/dist/runtime/runChangeBus.js +83 -0
  149. package/dist/runtime/runManager.d.ts +4 -0
  150. package/dist/runtime/runManager.js +36 -16
  151. package/dist/runtime/settingsFile.d.ts +12 -0
  152. package/dist/runtime/settingsFile.js +87 -6
  153. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  154. package/dist/runtime/stageAttemptBootstrap.js +18 -7
  155. package/dist/runtime/stageProcessLauncher.js +3 -0
  156. package/dist/runtime/stageRecovery.js +3 -1
  157. package/dist/runtime/stageRunner.d.ts +1 -0
  158. package/dist/runtime/stageRunner.js +17 -15
  159. package/dist/runtime/stageWorker.js +13 -10
  160. package/dist/server/bootstrap.d.ts +32 -0
  161. package/dist/server/bootstrap.js +56 -0
  162. package/dist/server/createHttpHost.d.ts +34 -0
  163. package/dist/server/createHttpHost.js +70 -0
  164. package/dist/server/http.d.ts +6 -13
  165. package/dist/server/http.js +297 -352
  166. package/dist/server/mcpHost.d.ts +22 -0
  167. package/dist/server/mcpHost.js +21 -0
  168. package/dist/server/operatorResults.d.ts +13 -0
  169. package/dist/server/operatorResults.js +22 -0
  170. package/dist/tools/emitStageEnvelope.d.ts +12 -1
  171. package/dist/tools/emitStageEnvelope.js +27 -1
  172. package/dist/types/envelope.d.ts +3 -0
  173. package/dist/types/forkChoice.d.ts +30 -0
  174. package/dist/types/forkChoice.js +1 -0
  175. package/dist/types/pipeline.d.ts +59 -2
  176. package/dist/types/stageflowManifest.d.ts +23 -0
  177. package/dist/types/stageflowManifest.js +1 -0
  178. package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
  179. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  180. package/dist/ui/index.html +3 -2
  181. package/dist/ui/stageflow-icon.svg +12 -0
  182. package/package.json +8 -5
  183. package/skills/install-suite.sh +107 -0
  184. package/skills/stageflow/SKILL.md +26 -0
  185. package/skills/stageflow/references/control-surface.md +26 -0
  186. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  187. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  188. package/skills/stageflow-author/SKILL.md +58 -0
  189. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  190. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  191. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  192. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  193. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  194. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  195. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  196. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  197. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  198. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  199. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  200. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  201. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  202. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  203. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  204. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  205. package/skills/stageflow-delegate/SKILL.md +18 -0
  206. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  207. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  208. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  209. package/skills/stageflow-run/SKILL.md +151 -0
  210. package/skills/stageflow-run/references/mcp-call.md +17 -0
  211. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  212. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  213. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  214. package/skills/stageflow-session-capture/SKILL.md +94 -0
  215. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  216. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  217. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  218. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  219. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  220. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  221. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  222. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  223. package/skills/stageflow-setup/SKILL.md +110 -0
  224. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
package/README.md CHANGED
@@ -1,12 +1,55 @@
1
- # Stageflow
1
+ # <img src="ui/public/stageflow-icon.svg" alt="" width="44" height="44" valign="middle"> Stageflow
2
2
 
3
- CLI pipeline runtime for configurable SDLC stages on [Pi](https://github.com/badlogic/pi-mono), plus a local operator console.
3
+ CLI pipeline runtime for **configurable stages** on [Pi](https://github.com/badlogic/pi-mono), with a local operator console.
4
4
 
5
- Author YAML pipelines, stages, and tasks in a project directory. Stageflow runs each stage in a fresh Pi agent session, writes a structured handoff envelope, and keeps run state under `.stageflow/`. Bins: **`sf`** and **`stageflow`**.
5
+ [![npm version](https://img.shields.io/npm/v/stageflow)](https://www.npmjs.com/package/stageflow)
6
+ [![npm downloads](https://img.shields.io/npm/dm/stageflow)](https://www.npmjs.com/package/stageflow)
7
+ ![Node >=20](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)
8
+ [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+ [![GitHub issues](https://img.shields.io/github/issues/tejasghutukade/stageflow)](https://github.com/tejasghutukade/stageflow/issues)
10
+ [![CI](https://img.shields.io/github/actions/workflow/status/tejasghutukade/stageflow/ci.yml?branch=main)](https://github.com/tejasghutukade/stageflow/actions/workflows/ci.yml)
11
+
12
+ Author pipeline-owned YAML in your project. Stageflow runs each stage in a fresh Pi agent session, writes a structured handoff envelope, and keeps run state under `.stageflow/`. Bins: **`sf`** and **`stageflow`**.
13
+
14
+ ## Why Stageflow?
15
+
16
+ Multi-step agent work breaks down when every handoff is ad hoc — a shell script here, a chat transcript there, no shared contract between steps. You end up re-explaining context, losing artifacts, and unable to run the same flow locally and in CI.
17
+
18
+ Stageflow treats **stages as the unit of composition**. You author pipeline-owned YAML — `*.pipeline.yaml`, `*.task.yaml`, optional `stageflow.yaml` manifest — and define whatever workflow fits your domain: release automation, research pipelines, content review, SDLC, ops runbooks, or something entirely custom. Each stage runs in a **fresh Pi session**, emits a typed **envelope** for the next stage, and can pause on **human-in-the-loop (HITL)** gates when you need an operator in the loop.
19
+
20
+ The same pipeline runs three ways without rewriting anything:
21
+
22
+ - **Locally** — `sf ui` for triage, provider setup, and gate replies
23
+ - **Headless / CI** — `sf validate` and `sf run --json` with predictable exit codes
24
+ - **Via MCP** — Streamable HTTP tools when the console is running
25
+
26
+ **Stageflow is not an SDLC tool.** Software delivery is a popular pattern in fixtures and dogfood flows, but stages are user-authored and domain-agnostic. If you can express a multi-step workflow in YAML, Stageflow can run it on Pi.
27
+
28
+ ## Features
29
+
30
+ - **Pipeline-owned YAML** — `*.pipeline.yaml` with inline stages or `uses:` refs; separate `*.task.yaml` files; optional repo-root `stageflow.yaml` manifest
31
+ - **Path-based CLI** — `--pipeline` and `--task` take filesystem paths (no bare-id lookup)
32
+ - **Pi-native** — runs on `@earendil-works/pi-coding-agent`; reuse an existing Pi login (`pi_home`) or store credentials in `~/.stageflow/agent/auth.json` (`sf_owned`)
33
+ - **Envelope handoffs** — typed stage payloads and artifacts via `write_stage_artifact` / `emit_stage_envelope`
34
+ - **HITL gates** — operator questions in the console; CI exits `2` when a run is waiting
35
+ - **Operator console** — triage runs, connect providers, answer gates, inspect transcripts at `http://127.0.0.1:3847`
36
+ - **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` is running
37
+ - **CI / headless** — `sf validate --strict --json`, `sf run --json` with exit codes `0` / `1` / `2`
38
+ - **Parallel stages** — pipeline DAG with fan-out and join (see [YAML catalog](docs/yaml-catalog.md))
39
+ - **Clonable fan-out** — clone one successor N times at completion, then join (see [YAML catalog](docs/yaml-catalog.md#clonable-successors))
40
+ - **SQLite run store** — `<git-root>/.stageflow/` state plus per-run workspaces under `.stageflow/runs/`
41
+
42
+ ## Installation
6
43
 
7
44
  Requires **Node.js ≥ 20**.
8
45
 
9
- ## Install
46
+ **Quick install (macOS / Linux):**
47
+
48
+ ```bash
49
+ curl -fsSL https://raw.githubusercontent.com/tejasghutukade/stageflow/main/install.sh | bash
50
+ ```
51
+
52
+ **npm:**
10
53
 
11
54
  ```bash
12
55
  npm i -g stageflow
@@ -18,20 +61,77 @@ npm i -g ./stageflow-*.tgz
18
61
 
19
62
  `better-sqlite3` ships prebuilds for common platforms. `--ignore-scripts` is fine when a prebuild exists. Benign `node-gyp` warnings during install can be ignored if `require("better-sqlite3")` works.
20
63
 
64
+ **Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
65
+
66
+ ```bash
67
+ npx skills add tejasghutukade/stageflow
68
+ ```
69
+
70
+ Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
71
+
21
72
  ## Quick start
22
73
 
23
- In a project directory, author:
74
+ In a project directory (preferably a git repo):
75
+
76
+ ```bash
77
+ sf init
78
+ ```
79
+
80
+ This scaffolds `stageflow.yaml`, `pipelines/hello.pipeline.yaml` (inline stage), and `tasks/hello.task.yaml`.
24
81
 
25
- - `pipelines/`ordered stage lists
26
- - `stages/` — stage definitions (instructions, model, optional human gates)
27
- - `tasks/` — work items (goal, pipeline, optional checkout)
82
+ Run (after connecting a provider see below):
28
83
 
29
84
  ```bash
30
85
  sf ui # operator console at http://127.0.0.1:3847
31
- sf run --task tasks/foo.yaml --pipeline <pipeline-id>
86
+ sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
87
+ ```
88
+
89
+ Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
90
+
91
+ ## Connect a model provider
92
+
93
+ Each stage sets a **`model`** id in YAML (e.g. `anthropic/claude-sonnet-4-5`). The matching provider must be authenticated before runs succeed — `sf validate` does not check auth.
94
+
95
+ **Operator console** (easiest for local setup):
96
+
97
+ ```bash
98
+ sf ui
32
99
  ```
33
100
 
34
- Connect model providers in the console (Settings → Providers) or via `sf providers …`. Stageflow is a thin Pi shell: reuse an existing Pi login (`pi_home`) or store credentials in an SF-owned file (`sf_owned`). You do not need Pi CLI `/login` as a hard prerequisite.
101
+ Open **Settings → Providers** (or **Connect** from the rail when nothing is configured) and sign in with API key or OAuth.
102
+
103
+ **CLI** (works headless and in CI):
104
+
105
+ ```bash
106
+ sf providers list
107
+ sf providers login anthropic --type api_key
108
+ sf providers login anthropic --type api_key --api-key-env ANTHROPIC_API_KEY
109
+ ```
110
+
111
+ Use `sf providers list` to see provider ids and supported auth types (`api_key`, `oauth`).
112
+
113
+ **Credential storage:** reuse Pi's shared auth file (`pi_home`) or keep credentials in Stageflow's global store (`sf_owned`):
114
+
115
+ ```bash
116
+ sf providers detect
117
+ sf providers source set pi_home # or sf_owned
118
+ ```
119
+
120
+ Stageflow is a thin Pi shell — you do not need Pi CLI `/login` as a hard prerequisite if you configure providers via the console or `sf providers`.
121
+
122
+ Full reference: [docs/providers.md](docs/providers.md)
123
+
124
+ ## Operator console
125
+
126
+ Start the console with `sf ui` (default `http://127.0.0.1:3847`).
127
+
128
+ - **Runs** — active and recent pipeline runs, capacity, and status at a glance
129
+ - **Run detail** — stage timeline, transcripts, envelope payloads, and artifact paths
130
+ - **HITL reply** — answer operator gates (`ask_operator`) without leaving the browser
131
+ - **Pipelines** — browse manifest-declared pipeline definitions
132
+ - **Settings → Providers** — connect model providers (`pi_home` or `sf_owned` credential storage)
133
+
134
+ *Screenshot coming soon — capture after console polish lands (see `docs/img/`).*
35
135
 
36
136
  ## Headless / CI
37
137
 
@@ -50,7 +150,7 @@ sf providers login <providerId> --api-key-env <VAR>
50
150
  If the provider also supports OAuth, pass `--type api_key`.
51
151
 
52
152
  ```bash
53
- sf run --task tasks/foo.yaml --pipeline <pipeline-id> --json
153
+ sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml --json
54
154
  ```
55
155
 
56
156
  The process exits `0` when the Run succeeded, `1` when it failed (including a busy start), and `2` when waiting. `sf run --json` prints one stdout document. `ok` is true only for `succeeded`. Busy has no `runId`.
@@ -64,13 +164,74 @@ The process exits `0` when the Run succeeded, `1` when it failed (including a bu
64
164
 
65
165
  On a mixed Pipeline, default wait parks the Run (exit `2`). `--skip-gates` fails the Stage (exit `1`). A Pipeline with no HITL does not need the flag.
66
166
 
167
+ Post-run extraction (dogfooded in [Archify PR diagrams](examples/archify-on-pr/)):
168
+
169
+ ```bash
170
+ sf run ... --json --include stages > sf-run.json
171
+ sf envelope get --from sf-run.json --stage author-diagrams --format handoff --json
172
+ sf skills install --from-zip <url> --skill-name archify
173
+ ```
174
+
175
+ See [docs/ci.md](docs/ci.md) for the full CI recipe and [`.github/actions/sf-run`](.github/actions/sf-run) composite action.
176
+
67
177
  ## State
68
178
 
69
- Runtime state lives in **`.stageflow/`** (SQLite + per-run workspaces under `.stageflow/runs/`). If `.stageflow` is missing and `.software-factory` exists from an older install, the next store open renames it to `.stageflow` once.
179
+ Runtime state lives in **`<git-root>/.stageflow/`** when inside a git repository (SQLite + per-run workspaces under `.stageflow/runs/`). Global config and `sf_owned` auth live under **`~/.stageflow/`**. If `.stageflow` is missing and `.software-factory` exists from an older install, the next store open renames it to `.stageflow` once.
70
180
 
71
181
  ## MCP
72
182
 
73
- `sf ui` also serves a Streamable HTTP MCP endpoint at `http://127.0.0.1:3847/mcp` (URL printed on boot). Point a Cursor (or other) MCP client at that URL for tools like `list_pipelines`, `start_run`, and `get_run`.
183
+ `sf ui` also serves a Streamable HTTP MCP endpoint at `http://127.0.0.1:3847/mcp` (URL printed on boot). Point a Cursor (or other) MCP client at that URL.
184
+
185
+ Available tools: `list_pipelines`, `list_tasks`, `list_runs`, `get_health`, `start_run`, `get_run`, `read_artifact`.
186
+
187
+ Full reference: [docs/mcp.md](docs/mcp.md)
188
+
189
+ ## Stageflow vs Conductor
190
+
191
+ Both projects address multi-step agent workflows. They differ in orchestration model and runtime.
192
+
193
+ | | **Stageflow** | **Conductor** |
194
+ |---|---------------|---------------|
195
+ | **Model** | Configurable **stages** on Pi; pipeline-owned YAML (any domain) | Multi-**agent** workflow graph |
196
+ | **Orchestration** | Pipeline DAG + stage worker | Jinja routing, no LLM in router |
197
+ | **Unit of work** | Task → Pipeline → Stage attempts | Workflow → Agents |
198
+ | **Handoff** | Typed **envelope** + artifacts | Agent output → context |
199
+ | **Human gates** | Operator console + MCP | Dashboard + TUI fleet |
200
+ | **Runtime** | Node.js, Pi coding agent | Python, Copilot/Claude SDKs |
201
+ | **Best for** | Personal/team **multi-stage Pi workflows** you define (releases, research, SDLC, …) | Enterprise multi-agent workflows |
202
+
203
+ If you want deterministic YAML routing across many agents, look at [Conductor](https://github.com/microsoft/conductor). If you want stage-bound Pi runs with reviewable envelopes and an operator console for **workflows you author**, use Stageflow.
204
+
205
+ ## Examples
206
+
207
+ | Example | Description |
208
+ |---------|-------------|
209
+ | **[archify-on-pr](examples/archify-on-pr/)** | **Featured** — PR diagram automation: conditional fork, skill binding, envelope handoff, GHA deliver |
210
+ | [hello-world](examples/hello-world/) | Single stage, domain-neutral |
211
+ | [plan-review](examples/plan-review/) | Multi-stage with operator gate — SDLC-style **example** |
212
+ | [conditional-fork](examples/conditional-fork/) | Exclusive fork routing with operator branch choice |
213
+ | [github-release](examples/github-release/) | Dogfood: draft + publish GitHub Release |
214
+ | [ci-validate](examples/ci-validate/) | Strict validate in CI |
215
+
216
+ Index: [examples/README.md](examples/README.md)
217
+
218
+ ## Documentation
219
+
220
+ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.io/stageflow/)** (GitHub Pages — live after merge to `main` and Pages enabled). Source in [`docs/`](docs/).
221
+
222
+ | Doc | Description |
223
+ |-----|-------------|
224
+ | [docs/README.md](docs/README.md) | Documentation index |
225
+ | [docs/quickstart.md](docs/quickstart.md) | Expanded quick start |
226
+ | [docs/yaml-catalog.md](docs/yaml-catalog.md) | Pipelines, stages, tasks schema |
227
+ | [docs/cli-reference.md](docs/cli-reference.md) | `sf run`, `sf envelope`, `sf skills`, `sf validate`, `sf ui`, `sf providers` |
228
+ | [docs/envelopes.md](docs/envelopes.md) | Handoff envelope contract |
229
+ | [docs/hitl.md](docs/hitl.md) | Gate kinds, `--skip-gates`, exit code `2` |
230
+ | [docs/ci.md](docs/ci.md) | `--json`, env vars, GitHub Actions |
231
+ | [docs/mcp.md](docs/mcp.md) | MCP tool reference |
232
+ | [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
233
+ | [docs/operator-console.md](docs/operator-console.md) | Console IA and settings |
234
+ | [docs/compare-conductor.md](docs/compare-conductor.md) | Positioning deep dive |
74
235
 
75
236
  ## Develop from source
76
237
 
@@ -82,8 +243,12 @@ npm run build && npm run ui:build # ui:build builds the UI and copies assets i
82
243
  sf ui
83
244
  ```
84
245
 
246
+ Run tests: `npm test` and `npm run ui:test`. Typecheck: `npm run typecheck`.
247
+
85
248
  ## License
86
249
 
87
250
  MIT © Tejas G
88
251
 
89
- Issues: https://github.com/tejasghutukade/stageflow/issues
252
+ **Support:** [GitHub Issues](https://github.com/tejasghutukade/stageflow/issues) · [SUPPORT.md](SUPPORT.md)
253
+
254
+ **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
@@ -11,7 +11,11 @@
11
11
  * those Q&A bodies.
12
12
  */
13
13
  import type { AskOperatorAnswer, AskOperatorPrompt } from "../tools/askOperator.js";
14
- export declare const ACTIVITY_TEXT_LIMIT = 500;
14
+ export declare const ACTIVITY_TEXT_LIMIT = 2000;
15
+ export declare const ACTIVITY_TEXT_LIMIT_ENV = "STAGEFLOW_ACTIVITY_TEXT_LIMIT";
16
+ export declare const ACTIVITY_VERBOSE_ENV = "STAGEFLOW_ACTIVITY_VERBOSE";
17
+ export declare function readActivityTextLimit(env?: Record<string, string | undefined>, override?: number): number;
18
+ export declare function readActivityVerbose(env?: Record<string, string | undefined>, override?: boolean): boolean;
15
19
  export type StageActivityEvent = {
16
20
  event: "agent_start";
17
21
  } | {
@@ -29,6 +33,11 @@ export type StageActivityEvent = {
29
33
  toolCallId?: string;
30
34
  isError?: boolean;
31
35
  resultPreview?: string;
36
+ } | {
37
+ event: "tool_progress";
38
+ toolName: string;
39
+ toolCallId?: string;
40
+ textPreview?: string;
32
41
  } | {
33
42
  event: "message";
34
43
  role: string;
@@ -59,6 +68,8 @@ export type StageLifecycleEvent = {
59
68
  } | {
60
69
  event: "failed";
61
70
  reason: string;
71
+ } | {
72
+ event: "skipped";
62
73
  };
63
74
  export type StageLogLine = StageActivityEvent | StageLifecycleEvent;
64
75
  export declare function truncateActivityText(value: unknown, limit?: number): string | undefined;
@@ -10,8 +10,43 @@
10
10
  * ACTIVITY_TEXT_LIMIT applies only to tool/message *previews* — never to
11
11
  * those Q&A bodies.
12
12
  */
13
- export const ACTIVITY_TEXT_LIMIT = 500;
14
- export function truncateActivityText(value, limit = ACTIVITY_TEXT_LIMIT) {
13
+ export const ACTIVITY_TEXT_LIMIT = 2000;
14
+ export const ACTIVITY_TEXT_LIMIT_ENV = "STAGEFLOW_ACTIVITY_TEXT_LIMIT";
15
+ export const ACTIVITY_VERBOSE_ENV = "STAGEFLOW_ACTIVITY_VERBOSE";
16
+ export function readActivityTextLimit(env = process.env, override) {
17
+ if (override !== undefined) {
18
+ if (Number.isFinite(override) && override >= 1)
19
+ return Math.floor(override);
20
+ return ACTIVITY_TEXT_LIMIT;
21
+ }
22
+ const raw = env[ACTIVITY_TEXT_LIMIT_ENV];
23
+ if (raw === undefined || raw.trim() === "") {
24
+ return ACTIVITY_TEXT_LIMIT;
25
+ }
26
+ const n = Number.parseInt(raw, 10);
27
+ if (!Number.isFinite(n) || n < 1) {
28
+ return ACTIVITY_TEXT_LIMIT;
29
+ }
30
+ return n;
31
+ }
32
+ export function readActivityVerbose(env = process.env, override) {
33
+ if (override !== undefined) {
34
+ return override;
35
+ }
36
+ const raw = env[ACTIVITY_VERBOSE_ENV];
37
+ if (raw === undefined || raw.trim() === "") {
38
+ return false;
39
+ }
40
+ const normalized = raw.trim().toLowerCase();
41
+ if (normalized === "0" ||
42
+ normalized === "false" ||
43
+ normalized === "off" ||
44
+ normalized === "no") {
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ export function truncateActivityText(value, limit = readActivityTextLimit()) {
15
50
  if (value === undefined || value === null)
16
51
  return undefined;
17
52
  let text;
@@ -1,6 +1,9 @@
1
- import type { StageActivityEvent } from "./activity.js";
1
+ import { type StageActivityEvent } from "./activity.js";
2
+ export declare const TOOL_PROGRESS_THROTTLE_MS = 300;
2
3
  export type StageActivityObserver = {
3
4
  onAssistantTextDelta(delta: string): void;
5
+ onThinkingDelta(delta: string): void;
6
+ onToolPartialResult(accumulatedText: string): void;
4
7
  onStreamBoundary(): void;
5
8
  onActivity(activity: StageActivityEvent): void;
6
9
  dispose(): void;
@@ -8,6 +11,7 @@ export type StageActivityObserver = {
8
11
  export type CreateStageActivityObserverOptions = {
9
12
  onActivity?: (event: StageActivityEvent) => void;
10
13
  writeStderr?: boolean;
14
+ toolProgressThrottleMs?: number;
11
15
  };
12
16
  /**
13
17
  * Observe StageActivityEvent milestones and optional assistant text deltas.
@@ -1,4 +1,11 @@
1
- function writeActivityStderr(activity) {
1
+ import { truncateActivityText, } from "./activity.js";
2
+ export const TOOL_PROGRESS_THROTTLE_MS = 300;
3
+ function writeIndentedPreview(preview) {
4
+ for (const line of preview.split("\n")) {
5
+ process.stderr.write(` ${line}\n`);
6
+ }
7
+ }
8
+ function writeActivityStderr(activity, skipAssistantMessage) {
2
9
  switch (activity.event) {
3
10
  case "agent_start":
4
11
  process.stderr.write(" … agent started\n");
@@ -11,13 +18,33 @@ function writeActivityStderr(activity) {
11
18
  return;
12
19
  case "tool_start":
13
20
  process.stderr.write(` → ${activity.toolName}\n`);
21
+ if (activity.argsPreview) {
22
+ writeIndentedPreview(activity.argsPreview);
23
+ }
14
24
  return;
15
25
  case "tool_end":
16
26
  if (activity.isError) {
17
27
  process.stderr.write(` ✕ ${activity.toolName}\n`);
18
28
  }
29
+ else if (activity.resultPreview) {
30
+ process.stderr.write(` ← ${activity.toolName}\n`);
31
+ }
32
+ if (activity.resultPreview) {
33
+ writeIndentedPreview(activity.resultPreview);
34
+ }
35
+ return;
36
+ case "tool_progress":
19
37
  return;
20
38
  case "message":
39
+ if (activity.role === "assistant" && skipAssistantMessage) {
40
+ return;
41
+ }
42
+ if (activity.role === "thinking") {
43
+ return;
44
+ }
45
+ if (activity.text) {
46
+ writeIndentedPreview(activity.text);
47
+ }
21
48
  return;
22
49
  }
23
50
  }
@@ -28,39 +55,175 @@ function writeActivityStderr(activity) {
28
55
  */
29
56
  export function createStageActivityObserver(options = {}) {
30
57
  const writeStderr = options.writeStderr !== false;
31
- let inAssistantText = false;
32
- const endTextLine = () => {
33
- if (!inAssistantText) {
58
+ const toolProgressThrottleMs = options.toolProgressThrottleMs ?? TOOL_PROGRESS_THROTTLE_MS;
59
+ let openStream = "none";
60
+ let assistantStreamedForMessage = false;
61
+ let lastToolPartial = "";
62
+ let thinkingBuffer = "";
63
+ let openTool = null;
64
+ let toolProgressAccum = "";
65
+ let toolProgressPending = false;
66
+ let toolProgressTimer = null;
67
+ const endOpenStream = () => {
68
+ if (openStream === "none") {
34
69
  return;
35
70
  }
36
71
  if (writeStderr) {
37
72
  process.stderr.write("\n");
38
73
  }
39
- inAssistantText = false;
74
+ openStream = "none";
75
+ };
76
+ const flushThinking = () => {
77
+ const raw = thinkingBuffer;
78
+ thinkingBuffer = "";
79
+ if (!raw.trim()) {
80
+ return;
81
+ }
82
+ options.onActivity?.({
83
+ event: "message",
84
+ role: "thinking",
85
+ text: truncateActivityText(raw),
86
+ });
87
+ };
88
+ const emitToolProgress = () => {
89
+ if (!toolProgressPending || !openTool) {
90
+ toolProgressPending = false;
91
+ return;
92
+ }
93
+ const textPreview = truncateActivityText(toolProgressAccum);
94
+ toolProgressPending = false;
95
+ if (!textPreview?.trim()) {
96
+ return;
97
+ }
98
+ options.onActivity?.({
99
+ event: "tool_progress",
100
+ toolName: openTool.toolName,
101
+ toolCallId: openTool.toolCallId,
102
+ textPreview,
103
+ });
104
+ };
105
+ const cancelToolProgressTimer = () => {
106
+ if (toolProgressTimer !== null) {
107
+ clearTimeout(toolProgressTimer);
108
+ toolProgressTimer = null;
109
+ }
110
+ };
111
+ const flushToolProgress = () => {
112
+ cancelToolProgressTimer();
113
+ emitToolProgress();
114
+ };
115
+ const scheduleToolProgress = () => {
116
+ if (!openTool || !toolProgressAccum.trim()) {
117
+ return;
118
+ }
119
+ toolProgressPending = true;
120
+ if (toolProgressTimer !== null) {
121
+ return;
122
+ }
123
+ toolProgressTimer = setTimeout(() => {
124
+ toolProgressTimer = null;
125
+ emitToolProgress();
126
+ }, toolProgressThrottleMs);
40
127
  };
41
128
  return {
42
129
  onAssistantTextDelta(delta) {
43
130
  if (!writeStderr || !delta) {
44
131
  return;
45
132
  }
46
- if (!inAssistantText) {
133
+ assistantStreamedForMessage = true;
134
+ if (openStream !== "assistant") {
135
+ endOpenStream();
47
136
  process.stderr.write(" ");
48
- inAssistantText = true;
137
+ openStream = "assistant";
138
+ }
139
+ process.stderr.write(delta);
140
+ },
141
+ onThinkingDelta(delta) {
142
+ if (!delta) {
143
+ return;
144
+ }
145
+ thinkingBuffer += delta;
146
+ if (!writeStderr) {
147
+ return;
148
+ }
149
+ if (openStream !== "thinking") {
150
+ endOpenStream();
151
+ process.stderr.write(" ∴ ");
152
+ openStream = "thinking";
153
+ }
154
+ process.stderr.write(delta);
155
+ },
156
+ onToolPartialResult(accumulatedText) {
157
+ if (!accumulatedText) {
158
+ return;
159
+ }
160
+ toolProgressAccum = accumulatedText;
161
+ scheduleToolProgress();
162
+ if (!writeStderr) {
163
+ return;
164
+ }
165
+ const delta = accumulatedText.startsWith(lastToolPartial)
166
+ ? accumulatedText.slice(lastToolPartial.length)
167
+ : accumulatedText;
168
+ lastToolPartial = accumulatedText;
169
+ if (!delta) {
170
+ return;
171
+ }
172
+ if (openStream !== "tool") {
173
+ endOpenStream();
174
+ process.stderr.write(" │ ");
175
+ openStream = "tool";
49
176
  }
50
177
  process.stderr.write(delta);
51
178
  },
52
179
  onStreamBoundary() {
53
- endTextLine();
180
+ endOpenStream();
181
+ flushThinking();
182
+ flushToolProgress();
183
+ lastToolPartial = "";
54
184
  },
55
185
  onActivity(activity) {
56
- endTextLine();
186
+ flushThinking();
187
+ if (activity.event === "tool_end") {
188
+ flushToolProgress();
189
+ }
190
+ endOpenStream();
191
+ lastToolPartial = "";
192
+ if (activity.event === "turn_start") {
193
+ assistantStreamedForMessage = false;
194
+ }
195
+ if (activity.event === "tool_start") {
196
+ openTool = {
197
+ toolName: activity.toolName,
198
+ toolCallId: activity.toolCallId,
199
+ };
200
+ toolProgressAccum = "";
201
+ toolProgressPending = false;
202
+ cancelToolProgressTimer();
203
+ }
204
+ else if (activity.event === "tool_end") {
205
+ openTool = null;
206
+ toolProgressAccum = "";
207
+ toolProgressPending = false;
208
+ cancelToolProgressTimer();
209
+ }
210
+ const skipAssistantMessage = activity.event === "message" &&
211
+ activity.role === "assistant" &&
212
+ assistantStreamedForMessage;
213
+ if (activity.event === "message" &&
214
+ activity.role === "assistant") {
215
+ assistantStreamedForMessage = false;
216
+ }
57
217
  options.onActivity?.(activity);
58
218
  if (writeStderr) {
59
- writeActivityStderr(activity);
219
+ writeActivityStderr(activity, skipAssistantMessage);
60
220
  }
61
221
  },
62
222
  dispose() {
63
- endTextLine();
223
+ endOpenStream();
224
+ flushThinking();
225
+ flushToolProgress();
226
+ cancelToolProgressTimer();
64
227
  },
65
228
  };
66
229
  }
@@ -1,14 +1,5 @@
1
1
  import { type StageProviderSupport } from "./providerSupport.js";
2
- /**
3
- * Locate the pi-cursor-sdk extension entry without opening full global package
4
- * discovery. Stages stay sealed; only this allowlisted path is loaded.
5
- *
6
- * Resolution order:
7
- * 1. STAGEFLOW_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
8
- * 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
9
- * 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
10
- * 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
11
- */
2
+ export declare function cursorExtensionEntryInPackage(packageRoot: string): string | undefined;
12
3
  export declare function resolveCursorExtensionPath(): string | undefined;
13
4
  export declare function isCursorModelRef(modelRef: string): boolean;
14
5
  export declare const cursorProviderSupport: StageProviderSupport;
@@ -18,8 +18,19 @@ const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
18
18
  * 1. STAGEFLOW_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
19
19
  * 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
20
20
  * 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
21
+ * (`dist/index.js` for 0.3+, `src/index.ts` for older publishes)
21
22
  * 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
22
23
  */
24
+ const CURSOR_PACKAGE_ENTRIES = ["dist/index.js", "src/index.ts"];
25
+ export function cursorExtensionEntryInPackage(packageRoot) {
26
+ for (const rel of CURSOR_PACKAGE_ENTRIES) {
27
+ const full = path.join(packageRoot, rel);
28
+ if (existsSync(full)) {
29
+ return full;
30
+ }
31
+ }
32
+ return undefined;
33
+ }
23
34
  export function resolveCursorExtensionPath() {
24
35
  const fromEnv = process.env.STAGEFLOW_CURSOR_EXTENSION?.trim();
25
36
  if (fromEnv && existsSync(fromEnv)) {
@@ -29,16 +40,13 @@ export function resolveCursorExtensionPath() {
29
40
  if (fromSettings) {
30
41
  return fromSettings;
31
42
  }
32
- const npmEntry = path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-cursor-sdk", "src", "index.ts");
33
- if (existsSync(npmEntry)) {
43
+ const npmEntry = cursorExtensionEntryInPackage(path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-cursor-sdk"));
44
+ if (npmEntry) {
34
45
  return npmEntry;
35
46
  }
36
47
  const here = path.dirname(fileURLToPath(import.meta.url));
37
- const sibling = path.resolve(here, "../../../pi-cursor-sdk/src/index.ts");
38
- if (existsSync(sibling)) {
39
- return sibling;
40
- }
41
- return undefined;
48
+ const siblingRoot = path.resolve(here, "../../../pi-cursor-sdk");
49
+ return cursorExtensionEntryInPackage(siblingRoot);
42
50
  }
43
51
  export function isCursorModelRef(modelRef) {
44
52
  const slash = modelRef.indexOf("/");
@@ -60,7 +68,7 @@ function missingExtensionReason(modelRef) {
60
68
  return [
61
69
  `Model "${modelRef}" requires pi-cursor-sdk, but no extension entry was found.`,
62
70
  "Install with `pi install npm:pi-cursor-sdk`, or set STAGEFLOW_CURSOR_EXTENSION",
63
- "to the absolute path of pi-cursor-sdk/src/index.ts.",
71
+ "to the absolute path of pi-cursor-sdk/dist/index.js (or src/index.ts).",
64
72
  "Also ensure a Cursor SDK API key is available via Pi /login or CURSOR_API_KEY.",
65
73
  ].join(" ");
66
74
  }
@@ -130,8 +138,8 @@ function resolveFromPiSettings() {
130
138
  }
131
139
  }
132
140
  }
133
- const declared = path.join(packageRoot, "src", "index.ts");
134
- if (existsSync(declared)) {
141
+ const declared = cursorExtensionEntryInPackage(packageRoot);
142
+ if (declared) {
135
143
  return declared;
136
144
  }
137
145
  }