stageflow 0.16.0 → 0.18.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.
- package/README.md +2 -4
- package/dist/agent/claudeTools.d.ts +1 -4
- package/dist/agent/claudeTools.js +1 -6
- package/dist/agent/fakeAgent.js +4 -5
- package/dist/agent/piAdapter.js +1 -14
- package/dist/config/cloneChain.d.ts +12 -0
- package/dist/config/cloneChain.js +322 -0
- package/dist/config/createPipeline.js +101 -21
- package/dist/config/createStage.js +7 -0
- package/dist/config/loadOutcome.d.ts +1 -0
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +34 -22
- package/dist/config/loadStage.d.ts +2 -0
- package/dist/config/loadStage.js +135 -125
- package/dist/config/migrateYaml.js +14 -3
- package/dist/config/normalizePipelineStageEntry.d.ts +5 -9
- package/dist/config/normalizePipelineStageEntry.js +133 -60
- package/dist/config/parsePreEmitChecks.js +2 -2
- package/dist/config/pipelineNeeds.d.ts +15 -2
- package/dist/config/pipelineNeeds.js +86 -4
- package/dist/config/pipelineRoute.d.ts +46 -0
- package/dist/config/pipelineRoute.js +215 -0
- package/dist/config/pipelineStageKeys.d.ts +2 -0
- package/dist/config/pipelineStageKeys.js +15 -4
- package/dist/config/printTargetYaml.js +18 -12
- package/dist/config/resolveForkEmitContext.d.ts +1 -1
- package/dist/config/resolveForkEmitContext.js +3 -26
- package/dist/config/resolvePipelineDag.d.ts +1 -3
- package/dist/config/resolvePipelineDag.js +143 -186
- package/dist/config/routeIf.d.ts +24 -0
- package/dist/config/routeIf.js +247 -0
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +5 -3
- package/dist/config/yamlDialect.d.ts +5 -8
- package/dist/config/yamlDialect.js +37 -29
- package/dist/envelope/check.d.ts +0 -2
- package/dist/envelope/check.js +1 -57
- package/dist/envelope/feedbackLoop.js +0 -3
- package/dist/envelope/forkChoice.js +1 -1
- package/dist/envelope/payloadSchema.d.ts +14 -0
- package/dist/envelope/payloadSchema.js +87 -2
- package/dist/mcp/catalogTools.js +1 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +0 -2
- package/dist/projection/projectRun.js +0 -3
- package/dist/prompt/priorEnvelope.js +1 -1
- package/dist/runstore/stageInstanceId.d.ts +1 -0
- package/dist/runstore/stageInstanceId.js +9 -0
- package/dist/runtime/cloneSchedule.d.ts +16 -33
- package/dist/runtime/cloneSchedule.js +155 -236
- package/dist/runtime/envelopeRouting.js +43 -94
- package/dist/runtime/forkGeneration.d.ts +2 -2
- package/dist/runtime/forkGeneration.js +29 -15
- package/dist/runtime/joinReadiness.d.ts +16 -0
- package/dist/runtime/joinReadiness.js +156 -0
- package/dist/runtime/pipelineScheduler.d.ts +0 -3
- package/dist/runtime/pipelineScheduler.js +81 -65
- package/dist/runtime/routeIfEval.d.ts +3 -0
- package/dist/runtime/routeIfEval.js +94 -0
- package/dist/runtime/runRetryCoordinator.js +0 -4
- package/dist/runtime/stageAttemptBootstrap.js +60 -11
- package/dist/runtime/taskInput.js +1 -13
- package/dist/tools/emitStageEnvelope.d.ts +2 -3
- package/dist/tools/emitStageEnvelope.js +1 -65
- package/dist/types/envelope.d.ts +0 -2
- package/dist/types/forkChoice.d.ts +0 -16
- package/dist/types/pipeline.d.ts +86 -27
- package/dist/types/stage.d.ts +11 -7
- package/dist/ui/assets/{index-D2Wt8hwH.css → index-DsgVjLUS.css} +1 -1
- package/dist/ui/assets/{index-XWJovnp7.js → index-DwKGaQvQ.js} +13 -12
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/stageflow-author/SKILL.md +4 -4
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -2
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -4
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +6 -4
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +9 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +3 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +8 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +3 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +8 -0
- package/skills/stageflow-author/references/catalog-mapping.md +159 -50
- package/skills/stageflow-author/references/catalog-write-conventions.md +3 -3
- package/skills/stageflow-author/references/stage-prompt-template.md +57 -17
- package/skills/stageflow-session-capture/SKILL.md +2 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +3 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +15 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +3 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -2
- package/dist/envelope/cloneForks.d.ts +0 -2
- package/dist/envelope/cloneForks.js +0 -77
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Stage prompt template
|
|
2
2
|
|
|
3
|
-
Every external stage file has `id` and `
|
|
3
|
+
Every external stage file has `id`, `system_prompt`, `io.input.schema`, and `io.output.schema`. Filename stem matches `id`. `model` is optional when the pipeline or `stageflow.yaml` supplies it — otherwise set it on the stage. Default when writing a concrete string is `anthropic/claude-sonnet-4-5`. When the human names a different model, write that string verbatim. Before writing models, confirm at least one provider is `configured` (see the skill Provider gate). Sibling stages after fan-out should use the same configured model family unless the human asks otherwise.
|
|
4
4
|
|
|
5
5
|
## Base
|
|
6
6
|
|
|
7
|
-
Use for every stage that is not a review point
|
|
7
|
+
Use for every stage that is not a review point. Branching is catalog `if` on the source `route`, not an agent choice. Base stages do not name successor ids.
|
|
8
8
|
|
|
9
9
|
```yaml
|
|
10
10
|
id: <id>
|
|
@@ -18,6 +18,13 @@ system_prompt: |
|
|
|
18
18
|
attempt must be emit_stage_envelope (or ask_operator if waiting on HITL).
|
|
19
19
|
An empty final message is a stage failure.
|
|
20
20
|
model: anthropic/claude-sonnet-4-5
|
|
21
|
+
io:
|
|
22
|
+
input:
|
|
23
|
+
schema:
|
|
24
|
+
type: object
|
|
25
|
+
output:
|
|
26
|
+
schema:
|
|
27
|
+
type: object
|
|
21
28
|
```
|
|
22
29
|
|
|
23
30
|
The prompt has no `ask_operator` line. It ends on the `emit_stage_envelope` instruction. Call `emit_stage_envelope` once per attempt.
|
|
@@ -46,7 +53,7 @@ Set artifacts: [] on emit. Put that path in payload.<field>.
|
|
|
46
53
|
|
|
47
54
|
## Summary and payload
|
|
48
55
|
|
|
49
|
-
Add to every stage (base, gated, or
|
|
56
|
+
Add to every stage (base, gated, or route-if):
|
|
50
57
|
|
|
51
58
|
```
|
|
52
59
|
summary and payload are handed to the next stage verbatim. State outcomes and
|
|
@@ -75,6 +82,13 @@ system_prompt: |
|
|
|
75
82
|
4. Call emit_stage_envelope with an advancing success status only after the
|
|
76
83
|
operator accepts. Never emit before accept.
|
|
77
84
|
model: anthropic/claude-sonnet-4-5
|
|
85
|
+
io:
|
|
86
|
+
input:
|
|
87
|
+
schema:
|
|
88
|
+
type: object
|
|
89
|
+
output:
|
|
90
|
+
schema:
|
|
91
|
+
type: object
|
|
78
92
|
```
|
|
79
93
|
|
|
80
94
|
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.
|
|
@@ -87,28 +101,54 @@ same attempt (commit, push, open PR as required), write any final artifact,
|
|
|
87
101
|
then emit. Do not pause after accept. Do not ask_operator for emit/schema errors.
|
|
88
102
|
```
|
|
89
103
|
|
|
90
|
-
##
|
|
104
|
+
## Route if
|
|
91
105
|
|
|
92
|
-
When the
|
|
106
|
+
When the source `route` uses `if`, the success payload must include the required discriminator field(s) named in `if.field`. Those fields must be required in `io.output.schema`. The agent emits the field values; it does not name successor ids and does not emit `fork_choice`. Listed `to:` stay on the DAG; `if` decides which run.
|
|
107
|
+
|
|
108
|
+
Add this block to the base (or gated) prompt:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
On success, emit the payload fields this stage's route if predicates read
|
|
112
|
+
(name them). Use the types in io.output.schema. Do not name successor stage
|
|
113
|
+
ids. Catalog if decides which children run.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Loop
|
|
117
|
+
|
|
118
|
+
When `route` has `{ type: loop }`, the success emit includes envelope field `feedback_loop`: `{ action: continue }` or `{ action: send_back, target: <ancestor id> }`. Catalog YAML uses `{ type: loop }` inside `route`, not a `feedback_loop:` key. `if` is not allowed on the loop entry.
|
|
119
|
+
|
|
120
|
+
Add this block to the base (or gated) prompt:
|
|
93
121
|
|
|
94
122
|
```
|
|
95
|
-
On
|
|
96
|
-
|
|
97
|
-
exactly one id. select: subset → one or more of those ids.
|
|
98
|
-
Normalize free_text answers before mapping (trim, lowercase, collapse spaces
|
|
99
|
-
and hyphens). If unmapped, ask_operator again; do not emit.
|
|
123
|
+
On success, include feedback_loop: { action: continue } to advance, or
|
|
124
|
+
{ action: send_back, target: <ancestor id> } to replay that ancestor.
|
|
100
125
|
```
|
|
101
126
|
|
|
102
|
-
|
|
127
|
+
## Clone Chain
|
|
128
|
+
|
|
129
|
+
A Clone Chain emitter succeeds by emitting a valid payload only. Do not emit `clone_forks`. The Clone Array field in `io.output.schema` is the list; Stageflow mints one Clone Instance per element.
|
|
103
130
|
|
|
104
|
-
|
|
131
|
+
The clone child's opening input is that array element (the named `$ref`) and nothing else. Prompt it as work on one item.
|
|
105
132
|
|
|
106
|
-
|
|
133
|
+
The Join receives the Clone Instance success envelopes (array order). It does not unpack a clone list from the emitter.
|
|
134
|
+
|
|
135
|
+
Add this block to an **emitter** prompt:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
On success, emit the list in the Clone Array field named in io.output.schema
|
|
139
|
+
(length 1..clone_cap). Do not emit clone_forks. Stageflow starts one run of
|
|
140
|
+
the next stage per element.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Add this block to a **clone child** prompt:
|
|
107
144
|
|
|
108
145
|
```
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
matching that successor's io.input.schema — not a stub object.
|
|
146
|
+
Your input is one list element. Do the work for that element only. Emit a
|
|
147
|
+
payload that matches io.output.schema. Do not emit a Clone Array.
|
|
112
148
|
```
|
|
113
149
|
|
|
114
|
-
|
|
150
|
+
Add this block to a **Join** prompt:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
You receive every Clone Instance success envelope. Gather them in list order.
|
|
154
|
+
```
|
|
@@ -74,8 +74,8 @@ Read [`references/catalog-authoring.md`](references/catalog-authoring.md) before
|
|
|
74
74
|
`node scripts/resolve-catalog-id.mjs --text "<phrase>" --dir <write-dir> --kind pipeline|stage`
|
|
75
75
|
|
|
76
76
|
Use the printed `{"id":"..."}`.
|
|
77
|
-
3. Write one external stage YAML per phase (`id`, `system_prompt`, optional `model`). End every `system_prompt` with an `emit_stage_envelope` footer. Omit `gate_kinds`. Filename stem must match `id`.
|
|
78
|
-
4. Write one pipeline YAML that wires the stages with `uses
|
|
77
|
+
3. Write one external stage YAML per phase (`id`, `system_prompt`, `io`, optional `model`). End every `system_prompt` with an `emit_stage_envelope` footer. Omit `gate_kinds`. Filename stem must match `id`.
|
|
78
|
+
4. Write one pipeline YAML that wires the stages with `uses:`, marks the first stage `entry: true`, and lists forward `route: [{ to: next }]` on each non-leaf in dependency order.
|
|
79
79
|
|
|
80
80
|
Do not write a `*.task.yaml`.
|
|
81
81
|
|
|
@@ -7,6 +7,21 @@ system_prompt: |
|
|
|
7
7
|
and a payload describing what you produced. Your last tool call in this
|
|
8
8
|
attempt must be emit_stage_envelope. An empty final message is a stage failure.
|
|
9
9
|
io:
|
|
10
|
+
input:
|
|
11
|
+
schema:
|
|
12
|
+
type: object
|
|
13
|
+
required:
|
|
14
|
+
- known
|
|
15
|
+
- unknown
|
|
16
|
+
properties:
|
|
17
|
+
known:
|
|
18
|
+
type: array
|
|
19
|
+
items:
|
|
20
|
+
type: string
|
|
21
|
+
unknown:
|
|
22
|
+
type: array
|
|
23
|
+
items:
|
|
24
|
+
type: string
|
|
10
25
|
output:
|
|
11
26
|
schema:
|
|
12
27
|
type: object
|
|
@@ -10,7 +10,7 @@ Resolve collision-safe ids with `scripts/resolve-catalog-id.mjs` (`--text`, `--d
|
|
|
10
10
|
|
|
11
11
|
## External stage file
|
|
12
12
|
|
|
13
|
-
Required fields: `id`, `system_prompt`. Filename stem must match `id` (`research.yaml` → `id: research`). `model` is optional when the pipeline or manifest supplies it.
|
|
13
|
+
Required fields: `id`, `system_prompt`, `io.input.schema`, `io.output.schema`. Filename stem must match `id` (`research.yaml` → `id: research`). `model` is optional when the pipeline or manifest supplies it.
|
|
14
14
|
|
|
15
15
|
Every `system_prompt` ends with a mandatory `emit_stage_envelope` footer, for example:
|
|
16
16
|
|
|
@@ -28,7 +28,9 @@ Worked example: `assets/example-pipeline/research.yaml` and `assets/example-pipe
|
|
|
28
28
|
|
|
29
29
|
## Pipeline file
|
|
30
30
|
|
|
31
|
-
`id` plus `stages:` entries with `id`, `uses:` (path relative to the pipeline file)
|
|
31
|
+
`id` plus `stages:` array of object entries with `id`, `uses:` (path relative to the pipeline file). Mark the first stage `entry: true`. Linear chain: each non-leaf lists `route: [{ to: next-id }]`. Optional pipeline-root `model` fills stages that omit `model`. Put body contracts (`io`, `verify`) on stage files; `io.input.schema` and `io.output.schema` are required on every stage. Put `on_verify_fail` on the pipeline entry when after-phase verify should repair or wait for an operator.
|
|
32
|
+
|
|
33
|
+
Session-capture is a linear chain of transcript phases. Exclusive branches, if needed, are catalog `if` on `route` after success, not agent-picked ids — see [`../../stageflow-author/references/catalog-mapping.md`](../../stageflow-author/references/catalog-mapping.md). `needs` and `fork` fail load.
|
|
32
34
|
|
|
33
35
|
Worked example: `assets/example-pipeline/example.pipeline.yaml`.
|
|
34
36
|
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { parseCloneForks } from "./check.js";
|
|
2
|
-
import { assertCloneAssignmentPayload } from "./payloadSchema.js";
|
|
3
|
-
import { EnvelopeError } from "../types/envelope.js";
|
|
4
|
-
import { CLONE_ACTIONS } from "../types/forkChoice.js";
|
|
5
|
-
function fieldPresent(item, key) {
|
|
6
|
-
return item[key] !== undefined;
|
|
7
|
-
}
|
|
8
|
-
export function assertCloneForks(params, cloneEmitContext) {
|
|
9
|
-
const record = params;
|
|
10
|
-
if (record.status === "failure")
|
|
11
|
-
return;
|
|
12
|
-
if (record.clone_forks === undefined || !Array.isArray(record.clone_forks)) {
|
|
13
|
-
throw new EnvelopeError("clonable successor requires clone_forks on success");
|
|
14
|
-
}
|
|
15
|
-
const items = parseCloneForks(record.clone_forks);
|
|
16
|
-
if (items.length === 0 && cloneEmitContext.clonableSuccessors.length > 0) {
|
|
17
|
-
throw new EnvelopeError("clone_forks must not be empty when clonable successors exist");
|
|
18
|
-
}
|
|
19
|
-
const expectedIds = cloneEmitContext.clonableSuccessors.map((s) => s.successorId);
|
|
20
|
-
const expectedSet = new Set(expectedIds);
|
|
21
|
-
const seen = new Set();
|
|
22
|
-
for (const [i, item] of items.entries()) {
|
|
23
|
-
const itemPath = `clone_forks[${i}]`;
|
|
24
|
-
if (seen.has(item.successor_id)) {
|
|
25
|
-
throw new EnvelopeError(`${itemPath} contains duplicate successor_id: ${item.successor_id}`);
|
|
26
|
-
}
|
|
27
|
-
if (!expectedSet.has(item.successor_id)) {
|
|
28
|
-
throw new EnvelopeError(`${itemPath} contains extra successor_id: ${item.successor_id}`);
|
|
29
|
-
}
|
|
30
|
-
seen.add(item.successor_id);
|
|
31
|
-
const capEntry = cloneEmitContext.clonableSuccessors.find((s) => s.successorId === item.successor_id);
|
|
32
|
-
const cloneCap = capEntry?.cloneCap;
|
|
33
|
-
const allowedActions = cloneEmitContext.allowedActions ?? [...CLONE_ACTIONS];
|
|
34
|
-
if (!allowedActions.includes(item.action)) {
|
|
35
|
-
throw new EnvelopeError(`${itemPath} action "${item.action}" is not allowed for ${item.successor_id} (allowed: ${allowedActions.join(", ")})`);
|
|
36
|
-
}
|
|
37
|
-
if (item.action === "skip") {
|
|
38
|
-
if (fieldPresent(item, "envelope") ||
|
|
39
|
-
fieldPresent(item, "mode") ||
|
|
40
|
-
fieldPresent(item, "clones")) {
|
|
41
|
-
throw new EnvelopeError(`${itemPath} skip for ${item.successor_id} must not include envelope, mode, or clones`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else if (item.action === "once") {
|
|
45
|
-
if (!fieldPresent(item, "envelope")) {
|
|
46
|
-
throw new EnvelopeError(`${itemPath} once for ${item.successor_id} requires envelope`);
|
|
47
|
-
}
|
|
48
|
-
if (fieldPresent(item, "mode") || fieldPresent(item, "clones")) {
|
|
49
|
-
throw new EnvelopeError(`${itemPath} once for ${item.successor_id} must not include mode or clones`);
|
|
50
|
-
}
|
|
51
|
-
if (capEntry?.cloneInputSchema !== undefined) {
|
|
52
|
-
assertCloneAssignmentPayload(item.envelope, capEntry.cloneInputSchema, item.successor_id, `${itemPath}.envelope`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (item.action === "fanout") {
|
|
56
|
-
if (fieldPresent(item, "envelope")) {
|
|
57
|
-
throw new EnvelopeError(`${itemPath} fanout for ${item.successor_id} must not include a top-level envelope`);
|
|
58
|
-
}
|
|
59
|
-
if (!fieldPresent(item, "mode") || !fieldPresent(item, "clones") || item.clones === undefined) {
|
|
60
|
-
throw new EnvelopeError(`${itemPath} fanout for ${item.successor_id} requires mode and clones`);
|
|
61
|
-
}
|
|
62
|
-
if (cloneCap === undefined || item.clones.length < 2 || item.clones.length > cloneCap) {
|
|
63
|
-
throw new EnvelopeError(`${itemPath} fanout for ${item.successor_id} must have between 2 and ${cloneCap} clones`);
|
|
64
|
-
}
|
|
65
|
-
if (capEntry?.cloneInputSchema !== undefined) {
|
|
66
|
-
for (const [j, clone] of item.clones.entries()) {
|
|
67
|
-
assertCloneAssignmentPayload(clone.envelope, capEntry.cloneInputSchema, item.successor_id, `${itemPath}.clones[${j}].envelope`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const id of expectedIds) {
|
|
73
|
-
if (!seen.has(id)) {
|
|
74
|
-
throw new EnvelopeError(`clone_forks missing successor_id: ${id}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|