stageflow 0.16.0 → 0.19.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 (124) hide show
  1. package/README.md +2 -4
  2. package/dist/agent/activity.d.ts +4 -0
  3. package/dist/agent/claudeAdapter.js +38 -0
  4. package/dist/agent/claudeTools.d.ts +1 -4
  5. package/dist/agent/claudeTools.js +1 -6
  6. package/dist/agent/fakeAgent.js +7 -6
  7. package/dist/agent/piAdapter.js +34 -15
  8. package/dist/agent/port.d.ts +1 -1
  9. package/dist/agent/stageTimeout.d.ts +9 -0
  10. package/dist/agent/stageTimeout.js +24 -0
  11. package/dist/cli/graphCommand.d.ts +15 -0
  12. package/dist/cli/graphCommand.js +79 -0
  13. package/dist/cli/graphRender.d.ts +2 -0
  14. package/dist/cli/graphRender.js +85 -0
  15. package/dist/cli/runsCommand.d.ts +1 -1
  16. package/dist/cli/runsCommand.js +37 -0
  17. package/dist/cli.js +12 -0
  18. package/dist/config/cloneChain.d.ts +12 -0
  19. package/dist/config/cloneChain.js +322 -0
  20. package/dist/config/createPipeline.js +101 -21
  21. package/dist/config/createStage.js +7 -0
  22. package/dist/config/loadOutcome.d.ts +1 -0
  23. package/dist/config/loadPipeline.d.ts +2 -0
  24. package/dist/config/loadPipeline.js +34 -22
  25. package/dist/config/loadStage.d.ts +2 -0
  26. package/dist/config/loadStage.js +135 -125
  27. package/dist/config/migrateYaml.js +14 -3
  28. package/dist/config/normalizePipelineStageEntry.d.ts +5 -9
  29. package/dist/config/normalizePipelineStageEntry.js +133 -60
  30. package/dist/config/parsePreEmitChecks.js +2 -2
  31. package/dist/config/pipelineNeeds.d.ts +15 -2
  32. package/dist/config/pipelineNeeds.js +86 -4
  33. package/dist/config/pipelineRoute.d.ts +46 -0
  34. package/dist/config/pipelineRoute.js +215 -0
  35. package/dist/config/pipelineStageKeys.d.ts +2 -0
  36. package/dist/config/pipelineStageKeys.js +15 -4
  37. package/dist/config/printTargetYaml.js +18 -12
  38. package/dist/config/resolveForkEmitContext.d.ts +1 -1
  39. package/dist/config/resolveForkEmitContext.js +3 -26
  40. package/dist/config/resolvePipelineDag.d.ts +1 -3
  41. package/dist/config/resolvePipelineDag.js +143 -186
  42. package/dist/config/routeIf.d.ts +24 -0
  43. package/dist/config/routeIf.js +247 -0
  44. package/dist/config/validateCatalog.d.ts +1 -1
  45. package/dist/config/validateCatalog.js +5 -3
  46. package/dist/config/yamlDialect.d.ts +5 -8
  47. package/dist/config/yamlDialect.js +37 -29
  48. package/dist/envelope/check.d.ts +0 -2
  49. package/dist/envelope/check.js +1 -57
  50. package/dist/envelope/feedbackLoop.js +0 -3
  51. package/dist/envelope/forkChoice.js +1 -1
  52. package/dist/envelope/payloadSchema.d.ts +14 -0
  53. package/dist/envelope/payloadSchema.js +87 -2
  54. package/dist/mcp/catalogTools.js +1 -2
  55. package/dist/mcp/controlTools.js +17 -0
  56. package/dist/package-meta.d.ts +1 -1
  57. package/dist/package-meta.js +1 -1
  58. package/dist/projection/projectRun.d.ts +0 -2
  59. package/dist/projection/projectRun.js +0 -3
  60. package/dist/prompt/priorEnvelope.js +1 -1
  61. package/dist/runstore/port.js +2 -0
  62. package/dist/runstore/stageInstanceId.d.ts +1 -0
  63. package/dist/runstore/stageInstanceId.js +9 -0
  64. package/dist/runtime/cloneSchedule.d.ts +16 -33
  65. package/dist/runtime/cloneSchedule.js +155 -236
  66. package/dist/runtime/envelopeRouting.js +43 -94
  67. package/dist/runtime/forkGeneration.d.ts +6 -2
  68. package/dist/runtime/forkGeneration.js +45 -15
  69. package/dist/runtime/joinReadiness.d.ts +16 -0
  70. package/dist/runtime/joinReadiness.js +156 -0
  71. package/dist/runtime/pipelineScheduler.d.ts +0 -3
  72. package/dist/runtime/pipelineScheduler.js +103 -65
  73. package/dist/runtime/reopenSkipped.d.ts +13 -0
  74. package/dist/runtime/reopenSkipped.js +56 -0
  75. package/dist/runtime/resumeTimedOut.d.ts +33 -0
  76. package/dist/runtime/resumeTimedOut.js +165 -0
  77. package/dist/runtime/routeIfEval.d.ts +3 -0
  78. package/dist/runtime/routeIfEval.js +94 -0
  79. package/dist/runtime/runManager.d.ts +1 -0
  80. package/dist/runtime/runManager.js +76 -0
  81. package/dist/runtime/runRetryCoordinator.js +0 -4
  82. package/dist/runtime/stageAttemptBootstrap.js +60 -11
  83. package/dist/runtime/stageRecovery.js +2 -2
  84. package/dist/runtime/stageRunner.js +6 -2
  85. package/dist/runtime/taskInput.js +1 -13
  86. package/dist/server/http.js +18 -0
  87. package/dist/tools/askOperator.d.ts +1 -0
  88. package/dist/tools/askOperator.js +14 -11
  89. package/dist/tools/emitStageEnvelope.d.ts +3 -3
  90. package/dist/tools/emitStageEnvelope.js +4 -66
  91. package/dist/tools/prepareToolArguments.d.ts +4 -0
  92. package/dist/tools/prepareToolArguments.js +79 -0
  93. package/dist/types/envelope.d.ts +0 -2
  94. package/dist/types/forkChoice.d.ts +0 -16
  95. package/dist/types/pipeline.d.ts +86 -27
  96. package/dist/types/stage.d.ts +11 -7
  97. package/dist/ui/assets/{index-D2Wt8hwH.css → index-CSsDued1.css} +1 -1
  98. package/dist/ui/assets/index-DMVdl1qB.js +119 -0
  99. package/dist/ui/index.html +2 -2
  100. package/package.json +2 -1
  101. package/skills/stageflow-author/SKILL.md +4 -4
  102. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -2
  103. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -4
  104. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +6 -4
  105. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +9 -1
  106. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +3 -0
  107. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +8 -0
  108. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +5 -2
  109. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +8 -0
  110. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +3 -0
  111. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +5 -2
  112. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +8 -0
  113. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +8 -0
  114. package/skills/stageflow-author/references/catalog-mapping.md +159 -50
  115. package/skills/stageflow-author/references/catalog-write-conventions.md +3 -3
  116. package/skills/stageflow-author/references/stage-prompt-template.md +57 -17
  117. package/skills/stageflow-session-capture/SKILL.md +2 -2
  118. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +3 -1
  119. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +15 -0
  120. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +3 -0
  121. package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -2
  122. package/dist/envelope/cloneForks.d.ts +0 -2
  123. package/dist/envelope/cloneForks.js +0 -77
  124. package/dist/ui/assets/index-XWJovnp7.js +0 -118
@@ -14,12 +14,12 @@ import type { TSchema } from "typebox/type";
14
14
  import { type PreEmitCheckOptions } from "../envelope/preEmitChecks.js";
15
15
  import type { StageEnvelope } from "../types/envelope.js";
16
16
  import type { FeedbackLoopConfig } from "../types/pipeline.js";
17
- import { type CloneEmitContext, type ForkEmitContext } from "../types/forkChoice.js";
17
+ import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
18
18
  export type EmitCapture = {
19
19
  envelope?: StageEnvelope;
20
20
  error?: string;
21
21
  };
22
- export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext, cloneEmitContext?: CloneEmitContext, preEmitCheckOptions?: PreEmitCheckOptions, feedbackLoopEmitContext?: FeedbackLoopConfig): {
22
+ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext, _cloneEmitContext?: CloneEmitContext, preEmitCheckOptions?: PreEmitCheckOptions, feedbackLoopEmitContext?: FeedbackLoopConfig): {
23
23
  name: string;
24
24
  label: string;
25
25
  description: string;
@@ -29,7 +29,6 @@ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloa
29
29
  artifacts: Type.TArray<Type.TString>;
30
30
  payload: Type.TOptional<TSchema>;
31
31
  fork_choice: Type.TArray<Type.TString>;
32
- clone_forks?: Type.TArray<TSchema> | undefined;
33
32
  feedback_loop?: Type.TOptional<TSchema> | undefined;
34
33
  checklist_attestations: Type.TOptional<Type.TArray<Type.TObject<{
35
34
  check_id: Type.TString;
@@ -38,6 +37,7 @@ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloa
38
37
  stage_id: Type.TOptional<Type.TString>;
39
38
  notes: Type.TOptional<Type.TString>;
40
39
  }>;
40
+ prepareArguments: (args: unknown) => never;
41
41
  execute: (_toolCallId: string, params: unknown) => Promise<{
42
42
  content: {
43
43
  type: "text";
@@ -12,11 +12,10 @@
12
12
  import { Type } from "typebox";
13
13
  import { assertRequiredEnvelope, isAdvancingEnvelope, } from "../envelope/check.js";
14
14
  import { assertFeedbackLoopAction } from "../envelope/feedbackLoop.js";
15
- import { assertCloneForks } from "../envelope/cloneForks.js";
16
15
  import { normalizeForkChoice } from "../envelope/forkChoice.js";
17
16
  import { assertEnvelopePayload, compilePayloadSchema, } from "../envelope/payloadSchema.js";
18
17
  import { assertPreEmitChecks, } from "../envelope/preEmitChecks.js";
19
- import { CLONE_ACTIONS, } from "../types/forkChoice.js";
18
+ import { prepareEmitStageEnvelopeArguments } from "./prepareToolArguments.js";
20
19
  function stringLiteralsSchema(values) {
21
20
  if (values.length === 1) {
22
21
  return Type.Literal(values[0]);
@@ -42,56 +41,6 @@ function feedbackLoopActionSchema(context) {
42
41
  }, { additionalProperties: false }),
43
42
  ]);
44
43
  }
45
- function cloneForksItemSchema(cloneEmitContext) {
46
- const allowedActions = cloneEmitContext.allowedActions ?? [...CLONE_ACTIONS];
47
- const allowSkip = allowedActions.includes("skip");
48
- const allowOnce = allowedActions.includes("once");
49
- const allowFanout = allowedActions.includes("fanout");
50
- const variants = [];
51
- for (const successor of cloneEmitContext.clonableSuccessors) {
52
- const successorId = Type.Literal(successor.successorId);
53
- const nestedPayload = successor.cloneInputSchema !== undefined
54
- ? compilePayloadSchema(successor.cloneInputSchema)
55
- : undefined;
56
- const nestedEnvelope = stageEnvelopeSchema(nestedPayload);
57
- if (allowSkip) {
58
- variants.push(Type.Object({
59
- successor_id: successorId,
60
- action: Type.Literal("skip"),
61
- }, { additionalProperties: false }));
62
- }
63
- if (allowOnce) {
64
- variants.push(Type.Object({
65
- successor_id: successorId,
66
- action: Type.Literal("once"),
67
- envelope: nestedEnvelope,
68
- }));
69
- }
70
- if (allowFanout) {
71
- variants.push(Type.Object({
72
- successor_id: successorId,
73
- action: Type.Literal("fanout"),
74
- mode: Type.Union([
75
- Type.Literal("parallel"),
76
- Type.Literal("sequential"),
77
- ]),
78
- clones: Type.Array(Type.Object({
79
- envelope: nestedEnvelope,
80
- }), { minItems: 2, maxItems: successor.cloneCap }),
81
- }));
82
- }
83
- }
84
- if (variants.length === 0) {
85
- return Type.Object({
86
- successor_id: Type.String(),
87
- action: stringLiteralsSchema(allowedActions),
88
- });
89
- }
90
- if (variants.length === 1) {
91
- return variants[0];
92
- }
93
- return Type.Union(variants);
94
- }
95
44
  function toolResult(text, details, options) {
96
45
  return {
97
46
  content: [{ type: "text", text }],
@@ -100,7 +49,7 @@ function toolResult(text, details, options) {
100
49
  ...(options?.terminate ? { terminate: true } : {}),
101
50
  };
102
51
  }
103
- export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext, cloneEmitContext, preEmitCheckOptions, feedbackLoopEmitContext) {
52
+ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext, _cloneEmitContext, preEmitCheckOptions, feedbackLoopEmitContext) {
104
53
  const compiledPayload = payloadSchema !== undefined
105
54
  ? compilePayloadSchema(payloadSchema)
106
55
  : undefined;
@@ -118,13 +67,6 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
118
67
  fork_choice: forkEmitContext !== undefined && feedbackLoopEmitContext === undefined
119
68
  ? Type.Array(Type.String())
120
69
  : Type.Optional(Type.Array(Type.String())),
121
- ...(cloneEmitContext !== undefined
122
- ? {
123
- clone_forks: feedbackLoopEmitContext === undefined
124
- ? Type.Array(cloneForksItemSchema(cloneEmitContext))
125
- : Type.Optional(Type.Array(cloneForksItemSchema(cloneEmitContext))),
126
- }
127
- : {}),
128
70
  ...(feedbackLoopEmitContext !== undefined
129
71
  ? {
130
72
  feedback_loop: Type.Optional(feedbackLoopActionSchema(feedbackLoopEmitContext)),
@@ -137,12 +79,13 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
137
79
  stage_id: Type.Optional(Type.String()),
138
80
  notes: Type.Optional(Type.String()),
139
81
  }),
82
+ prepareArguments: prepareEmitStageEnvelopeArguments,
140
83
  execute: async (_toolCallId, params) => {
141
84
  if (capture.envelope) {
142
85
  return toolResult("Envelope already accepted; later emits are ignored.", { advancing: isAdvancingEnvelope(capture.envelope), error: "" }, { terminate: true });
143
86
  }
144
87
  try {
145
- const envelope = assertRequiredEnvelope(params);
88
+ const envelope = assertRequiredEnvelope(prepareEmitStageEnvelopeArguments(params));
146
89
  assertFeedbackLoopAction(envelope, feedbackLoopEmitContext);
147
90
  const isSendBack = envelope.feedback_loop?.action === "send_back";
148
91
  if (envelope.status !== "failure" &&
@@ -150,11 +93,6 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
150
93
  forkEmitContext !== undefined) {
151
94
  normalizeForkChoice(envelope.fork_choice, "emit", forkEmitContext);
152
95
  }
153
- if (envelope.status !== "failure" &&
154
- !isSendBack &&
155
- cloneEmitContext !== undefined) {
156
- assertCloneForks(envelope, cloneEmitContext);
157
- }
158
96
  assertEnvelopePayload(envelope, payloadSchema);
159
97
  if (envelope.status !== "failure") {
160
98
  await assertPreEmitChecks(envelope, preEmitCheckOptions);
@@ -0,0 +1,4 @@
1
+ export declare function coerceJsonArrayField(args: Record<string, unknown>, key: string): void;
2
+ export declare function coerceJsonObjectField(args: Record<string, unknown>, key: string): void;
3
+ export declare function prepareAskOperatorArguments(input: unknown): unknown;
4
+ export declare function prepareEmitStageEnvelopeArguments(input: unknown): unknown;
@@ -0,0 +1,79 @@
1
+ function isRecord(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function parseJsonArrayString(value) {
5
+ if (typeof value !== "string")
6
+ return undefined;
7
+ const trimmed = value.trim();
8
+ if (!trimmed.startsWith("["))
9
+ return undefined;
10
+ try {
11
+ const parsed = JSON.parse(trimmed);
12
+ return Array.isArray(parsed) ? parsed : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ function parseJsonObjectString(value) {
19
+ if (typeof value !== "string")
20
+ return undefined;
21
+ const trimmed = value.trim();
22
+ if (!trimmed.startsWith("{"))
23
+ return undefined;
24
+ try {
25
+ const parsed = JSON.parse(trimmed);
26
+ return isRecord(parsed) ? parsed : undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ export function coerceJsonArrayField(args, key) {
33
+ const parsed = parseJsonArrayString(args[key]);
34
+ if (parsed !== undefined) {
35
+ args[key] = parsed;
36
+ }
37
+ }
38
+ export function coerceJsonObjectField(args, key) {
39
+ const parsed = parseJsonObjectString(args[key]);
40
+ if (parsed !== undefined) {
41
+ args[key] = parsed;
42
+ }
43
+ }
44
+ function coerceJsonObjectArrayElements(args, key) {
45
+ const value = args[key];
46
+ if (!Array.isArray(value))
47
+ return;
48
+ args[key] = value.map((item) => parseJsonObjectString(item) ?? item);
49
+ }
50
+ export function prepareAskOperatorArguments(input) {
51
+ if (!isRecord(input))
52
+ return input;
53
+ const args = { ...input };
54
+ coerceJsonArrayField(args, "questions");
55
+ coerceJsonObjectArrayElements(args, "questions");
56
+ coerceJsonArrayField(args, "artifacts");
57
+ return args;
58
+ }
59
+ export function prepareEmitStageEnvelopeArguments(input) {
60
+ if (!isRecord(input))
61
+ return input;
62
+ const args = { ...input };
63
+ coerceJsonObjectField(args, "payload");
64
+ coerceJsonObjectField(args, "feedback_loop");
65
+ coerceJsonArrayField(args, "artifacts");
66
+ coerceJsonArrayField(args, "fork_choice");
67
+ coerceJsonArrayField(args, "checklist_attestations");
68
+ coerceJsonObjectArrayElements(args, "checklist_attestations");
69
+ if (Array.isArray(args.checklist_attestations)) {
70
+ args.checklist_attestations = args.checklist_attestations.map((item) => {
71
+ if (!isRecord(item))
72
+ return item;
73
+ const next = { ...item };
74
+ coerceJsonArrayField(next, "items");
75
+ return next;
76
+ });
77
+ }
78
+ return args;
79
+ }
@@ -1,4 +1,3 @@
1
- import type { CloneForkItem } from "./forkChoice.js";
2
1
  export type EnvelopeStatus = "success" | "failure";
3
2
  export type FeedbackLoopAction = {
4
3
  action: "continue";
@@ -12,7 +11,6 @@ export type StageEnvelope = {
12
11
  artifacts: string[];
13
12
  payload?: Record<string, unknown>;
14
13
  fork_choice?: string[];
15
- clone_forks?: CloneForkItem[];
16
14
  feedback_loop?: FeedbackLoopAction;
17
15
  /** Agent's explicit acknowledgement of pipeline-owned checklist items. */
18
16
  checklist_attestations?: Array<{
@@ -1,4 +1,3 @@
1
- import type { StageEnvelope } from "./envelope.js";
2
1
  export type ForkChoiceShape = {
3
2
  cardinality: "one" | "subset";
4
3
  allowNone: boolean;
@@ -9,21 +8,6 @@ export type ForkEmitContext = {
9
8
  };
10
9
  export declare const CLONE_ACTIONS: readonly ["skip", "once", "fanout"];
11
10
  export type CloneAction = (typeof CLONE_ACTIONS)[number];
12
- export type CloneForkItem = {
13
- successor_id: string;
14
- action: "skip";
15
- } | {
16
- successor_id: string;
17
- action: "once";
18
- envelope: StageEnvelope;
19
- } | {
20
- successor_id: string;
21
- action: "fanout";
22
- mode: "parallel" | "sequential";
23
- clones: Array<{
24
- envelope: StageEnvelope;
25
- }>;
26
- };
27
11
  export type CloneSuccessorEmit = {
28
12
  successorId: string;
29
13
  cloneCap: number;
@@ -1,4 +1,3 @@
1
- import type { CloneAction } from "./forkChoice.js";
2
1
  import type { LoadedStageConfig, StageGateKind, StageIoYaml } from "./stage.js";
3
2
  import type { CompletionContract, RecoveryPolicy } from "./completion.js";
4
3
  export type PipelineConfig = {
@@ -16,15 +15,75 @@ export type PipelineForkConfig = {
16
15
  allow_none: boolean;
17
16
  };
18
17
  export type NeedTerminalState = "succeeded" | "failed" | "skipped";
18
+ export type RouteIfOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "not_in";
19
+ export type RouteIfLeafPredicate = {
20
+ field: string;
21
+ op: RouteIfOp;
22
+ value: unknown;
23
+ };
24
+ export type RouteIfAllPredicate = {
25
+ all: RouteIfPredicate[];
26
+ field?: never;
27
+ };
28
+ export type RouteIfAnyPredicate = {
29
+ any: RouteIfPredicate[];
30
+ field?: never;
31
+ };
32
+ export type RouteIfNotPredicate = {
33
+ not: RouteIfPredicate;
34
+ field?: never;
35
+ };
36
+ export type RouteIfPredicate = RouteIfLeafPredicate | RouteIfAllPredicate | RouteIfAnyPredicate | RouteIfNotPredicate;
19
37
  export type PipelineNeedEdge = {
20
38
  id: string;
21
39
  on: NeedTerminalState[];
40
+ if?: RouteIfPredicate;
22
41
  };
23
42
  export type PipelineNeedItem = string | {
24
43
  id: string;
25
44
  on?: NeedTerminalState[];
26
45
  };
27
46
  export type PipelineNeeds = string | PipelineNeedEdge[];
47
+ /**
48
+ * Terminal states a route entry (or its inverted predecessor edge) can gate on.
49
+ * Same domain as `NeedTerminalState` — kept as a distinct alias since `route`
50
+ * is a separate vocabulary from `needs` (see docs/yaml-catalog.md).
51
+ */
52
+ export type RouteTerminalState = NeedTerminalState;
53
+ /**
54
+ * A forward route entry: hands off from the declaring stage to `to`, gated on
55
+ * the declaring stage's own terminal state (`on`, defaulting to succeeded-only).
56
+ */
57
+ export type PipelineRouteForwardEntry = {
58
+ to: string;
59
+ on?: RouteTerminalState | RouteTerminalState[];
60
+ if?: RouteIfPredicate;
61
+ };
62
+ /**
63
+ * A loop route entry (ticket 03, route-based-pipeline-wiring spec): sends
64
+ * execution back to a declared ancestor of the source stage instead of
65
+ * continuing forward, carrying the same replay policy `FeedbackLoopConfig`
66
+ * carries today — `target` renamed to `to` for naming consistency with
67
+ * forward entries. Contributes no forward DAG edge (`toRouteEdges` skips
68
+ * it), so it is excluded from cycle detection; it is a runtime-only replay
69
+ * schedule over the already-resolved forward graph.
70
+ */
71
+ export type PipelineRouteLoopEntry = {
72
+ type: "loop";
73
+ to: string;
74
+ max_replays: number;
75
+ on_max_replays: "require_continue" | "wait_for_human";
76
+ replay_session: "resume" | "new_session";
77
+ };
78
+ export type PipelineRouteEntry = PipelineRouteForwardEntry | PipelineRouteLoopEntry;
79
+ /** A stage's outbound `route` field: where execution goes next. */
80
+ export type PipelineRoute = PipelineRouteEntry[];
81
+ /** Normalized forward route edge (post-parse): `on` is always populated. */
82
+ export type PipelineRouteEdge = {
83
+ to: string;
84
+ on: RouteTerminalState[];
85
+ if?: RouteIfPredicate;
86
+ };
28
87
  /** Runtime feedback-loop policy declared by the stage that can send work back. */
29
88
  export type FeedbackLoopConfig = {
30
89
  target: string;
@@ -32,43 +91,44 @@ export type FeedbackLoopConfig = {
32
91
  on_max_replays: "require_continue" | "wait_for_human";
33
92
  replay_session: "resume" | "new_session";
34
93
  };
94
+ export type CloneMode = "parallel" | "sequential";
35
95
  export type PipelineStageRef = {
36
96
  id: string;
37
- needs?: string | PipelineNeedItem[];
38
- fork?: {
39
- select: "one" | "subset";
40
- allow_none?: boolean;
41
- };
42
- clonable?: boolean;
43
- clone_cap?: number;
44
97
  /** IR: after-phase checks. YAML: `verify` items whose `when` includes `after`. */
45
98
  completion?: CompletionContract;
46
99
  /** IR: after-phase failure policy. YAML: `on_verify_fail`. */
47
100
  recovery?: RecoveryPolicy;
48
- feedback_loop?: FeedbackLoopConfig;
49
101
  /** Omitted means this stage is safe to include in a feedback replay. */
50
102
  replay_safe?: boolean;
103
+ /** Pipeline wiring vocabulary (see docs/yaml-catalog.md). */
104
+ route?: PipelineRouteEntry[];
105
+ /** Marks this stage as a pipeline entry point. */
106
+ entry?: boolean;
107
+ clone_cap?: number;
108
+ clone_mode?: CloneMode;
51
109
  };
52
110
  /**
53
- * Pipeline YAML stage entry (both dialects). Target authoring is `io` / `verify`
54
- * / `on_verify_fail`. The payload_schema / pre_emit_checks / completion /
55
- * recovery keys are the legacy YAML dialect (and also the IR names after compile).
56
- * New catalog fields: add target YAML keys here and compile them in yamlDialect.ts.
111
+ * Catalog YAML stage entry (`io` / `verify` / `on_verify_fail`). Not IR.
112
+ * Dual-read of `payload_schema` / `pre_emit_checks` / `completion` / `recovery`
113
+ * stays in `legacyYaml.ts` on the raw record.
57
114
  */
58
- export type PipelineStageYamlEntry = PipelineStageRef & {
115
+ export type PipelineStageYamlEntry = {
116
+ id?: string;
59
117
  uses?: string;
60
118
  system_prompt?: string;
61
119
  model?: string;
62
- payload_schema?: unknown;
63
120
  gate_kinds?: StageGateKind[];
64
- clone_input_schema?: unknown;
65
- clone_actions?: CloneAction[];
121
+ timeout_ms?: number;
66
122
  skill?: string;
67
123
  mcp?: string[];
124
+ replay_safe?: boolean;
125
+ route?: PipelineRouteEntry[];
126
+ entry?: boolean;
127
+ clone_cap?: number;
128
+ clone_mode?: CloneMode;
68
129
  io?: StageIoYaml;
69
130
  verify?: unknown;
70
131
  on_verify_fail?: RecoveryPolicy;
71
- pre_emit_checks?: unknown;
72
132
  };
73
133
  export type PipelineIncludeEntry = {
74
134
  local: string;
@@ -79,20 +139,16 @@ export type PipelineFragmentConfig = {
79
139
  };
80
140
  export type NormalizedPipelineStageEntry = {
81
141
  id: string;
82
- needs?: PipelineNeeds;
83
- fork?: {
84
- select: "one" | "subset";
85
- allow_none?: boolean;
86
- };
87
- clonable?: boolean;
88
- clone_cap?: number;
89
142
  /** IR after compile. YAML `verify` after-phase / `on_verify_fail`. */
90
143
  completion?: CompletionContract;
91
144
  recovery?: RecoveryPolicy;
92
- feedback_loop?: FeedbackLoopConfig;
93
145
  replay_safe?: boolean;
146
+ route?: PipelineRouteEntry[];
147
+ entry?: boolean;
94
148
  skill?: string;
95
149
  mcp?: string[];
150
+ clone_cap?: number;
151
+ clone_mode?: CloneMode;
96
152
  body: {
97
153
  kind: "inline";
98
154
  raw: Record<string, unknown>;
@@ -116,8 +172,9 @@ export type ResolvedPipelineStageNode = {
116
172
  ancestors: string[];
117
173
  stageIndex: number;
118
174
  fork?: PipelineForkConfig;
119
- clonable?: boolean;
120
175
  clone_cap?: number;
176
+ clone_mode?: CloneMode;
177
+ clone_array_field?: string;
121
178
  definition_id?: string;
122
179
  /** Persisted on pipeline_dag. YAML `verify` after-phase. */
123
180
  completion?: CompletionContract;
@@ -125,6 +182,8 @@ export type ResolvedPipelineStageNode = {
125
182
  recovery?: RecoveryPolicy;
126
183
  feedback_loop?: FeedbackLoopConfig;
127
184
  replay_safe?: boolean;
185
+ /** Present (true) only when this stage was declared `entry: true`. */
186
+ entry?: boolean;
128
187
  };
129
188
  export type ResolvedPipelineDag = {
130
189
  nodes: ResolvedPipelineStageNode[];
@@ -1,14 +1,19 @@
1
1
  import type { PreEmitCheck } from "./preEmitCheck.js";
2
- import type { CloneAction } from "./forkChoice.js";
2
+ /** Typed emit/schema fields from `compileTargetContract`. Not catalog YAML keys. */
3
+ export type CompiledStageEmitBody = {
4
+ payload_schema?: unknown;
5
+ clone_input_schema?: unknown;
6
+ pre_emit_checks?: PreEmitCheck[];
7
+ };
3
8
  export declare const STAGE_GATE_KINDS: readonly ["free_text", "confirm", "multi_question", "artifact_backed"];
4
9
  export type StageGateKind = (typeof STAGE_GATE_KINDS)[number];
5
10
  /** Target YAML `io:` block. Compiles onto StageConfig.payload_schema / clone_input_schema. */
6
11
  export type StageIoYaml = {
7
- input?: {
8
- schema?: unknown;
12
+ input: {
13
+ schema: unknown;
9
14
  };
10
- output?: {
11
- schema?: unknown;
15
+ output: {
16
+ schema: unknown;
12
17
  };
13
18
  };
14
19
  /**
@@ -27,9 +32,8 @@ export type StageConfig = {
27
32
  gate_kinds?: StageGateKind[];
28
33
  /** IR: emit-phase checks. YAML: `verify` items whose `when` includes `emit`. */
29
34
  pre_emit_checks?: PreEmitCheck[];
30
- /** IR: clone/fan-out assignment schema. YAML: `io.input.schema`. */
35
+ /** IR: inbound assignment schema. YAML: `io.input.schema`. */
31
36
  clone_input_schema?: unknown;
32
- clone_actions?: CloneAction[];
33
37
  /** Optional stage wall-clock timeout in milliseconds (default 60 minutes). */
34
38
  timeout_ms?: number;
35
39
  skill?: string;