@tagma/sdk 0.7.4 → 0.7.5

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 (190) hide show
  1. package/README.md +60 -53
  2. package/dist/completions/file-exists.js +1 -1
  3. package/dist/completions/file-exists.js.map +1 -1
  4. package/dist/completions/output-check.d.ts.map +1 -1
  5. package/dist/completions/output-check.js +17 -4
  6. package/dist/completions/output-check.js.map +1 -1
  7. package/dist/config.d.ts +4 -4
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +2 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/dataflow.d.ts +3 -0
  12. package/dist/dataflow.d.ts.map +1 -0
  13. package/dist/dataflow.js +2 -0
  14. package/dist/dataflow.js.map +1 -0
  15. package/dist/drivers/opencode.d.ts.map +1 -1
  16. package/dist/drivers/opencode.js +23 -71
  17. package/dist/drivers/opencode.js.map +1 -1
  18. package/dist/middlewares/static-context.d.ts.map +1 -1
  19. package/dist/middlewares/static-context.js +1 -2
  20. package/dist/middlewares/static-context.js.map +1 -1
  21. package/dist/pipeline-runner.d.ts.map +1 -1
  22. package/dist/pipeline-runner.js +2 -2
  23. package/dist/pipeline-runner.js.map +1 -1
  24. package/dist/schema.d.ts.map +1 -1
  25. package/dist/schema.js +3 -4
  26. package/dist/schema.js.map +1 -1
  27. package/dist/triggers/file.d.ts.map +1 -1
  28. package/dist/triggers/file.js +1 -2
  29. package/dist/triggers/file.js.map +1 -1
  30. package/dist/triggers/manual.d.ts.map +1 -1
  31. package/dist/triggers/manual.js +1 -2
  32. package/dist/triggers/manual.js.map +1 -1
  33. package/dist/types.d.ts +1 -2
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js +1 -12
  36. package/dist/types.js.map +1 -1
  37. package/dist/utils-api.d.ts +1 -1
  38. package/dist/utils-api.d.ts.map +1 -1
  39. package/dist/utils-api.js +1 -1
  40. package/dist/utils-api.js.map +1 -1
  41. package/dist/validate-raw.d.ts.map +1 -1
  42. package/dist/validate-raw.js +5 -12
  43. package/dist/validate-raw.js.map +1 -1
  44. package/package.json +11 -24
  45. package/dist/adapters/stdin-approval.d.ts +0 -2
  46. package/dist/adapters/stdin-approval.d.ts.map +0 -1
  47. package/dist/adapters/stdin-approval.js +0 -2
  48. package/dist/adapters/stdin-approval.js.map +0 -1
  49. package/dist/adapters/websocket-approval.d.ts +0 -2
  50. package/dist/adapters/websocket-approval.d.ts.map +0 -1
  51. package/dist/adapters/websocket-approval.js +0 -2
  52. package/dist/adapters/websocket-approval.js.map +0 -1
  53. package/dist/core/dataflow.d.ts +0 -23
  54. package/dist/core/dataflow.d.ts.map +0 -1
  55. package/dist/core/dataflow.js +0 -99
  56. package/dist/core/dataflow.js.map +0 -1
  57. package/dist/core/log-prune.d.ts +0 -16
  58. package/dist/core/log-prune.d.ts.map +0 -1
  59. package/dist/core/log-prune.js +0 -34
  60. package/dist/core/log-prune.js.map +0 -1
  61. package/dist/core/preflight.d.ts +0 -13
  62. package/dist/core/preflight.d.ts.map +0 -1
  63. package/dist/core/preflight.js +0 -61
  64. package/dist/core/preflight.js.map +0 -1
  65. package/dist/core/run-context.d.ts +0 -55
  66. package/dist/core/run-context.d.ts.map +0 -1
  67. package/dist/core/run-context.js +0 -158
  68. package/dist/core/run-context.js.map +0 -1
  69. package/dist/core/run-state.d.ts +0 -25
  70. package/dist/core/run-state.d.ts.map +0 -1
  71. package/dist/core/run-state.js +0 -93
  72. package/dist/core/run-state.js.map +0 -1
  73. package/dist/core/scheduler.d.ts +0 -13
  74. package/dist/core/scheduler.d.ts.map +0 -1
  75. package/dist/core/scheduler.js +0 -35
  76. package/dist/core/scheduler.js.map +0 -1
  77. package/dist/core/task-executor.d.ts +0 -13
  78. package/dist/core/task-executor.d.ts.map +0 -1
  79. package/dist/core/task-executor.js +0 -610
  80. package/dist/core/task-executor.js.map +0 -1
  81. package/dist/core/trigger-errors.d.ts +0 -9
  82. package/dist/core/trigger-errors.d.ts.map +0 -1
  83. package/dist/core/trigger-errors.js +0 -15
  84. package/dist/core/trigger-errors.js.map +0 -1
  85. package/dist/dag.d.ts +0 -45
  86. package/dist/dag.d.ts.map +0 -1
  87. package/dist/dag.js +0 -177
  88. package/dist/dag.js.map +0 -1
  89. package/dist/hooks.d.ts +0 -73
  90. package/dist/hooks.d.ts.map +0 -1
  91. package/dist/hooks.js +0 -106
  92. package/dist/hooks.js.map +0 -1
  93. package/dist/pipeline-definition.d.ts +0 -3
  94. package/dist/pipeline-definition.d.ts.map +0 -1
  95. package/dist/pipeline-definition.js +0 -4
  96. package/dist/pipeline-definition.js.map +0 -1
  97. package/dist/ports.d.ts +0 -196
  98. package/dist/ports.d.ts.map +0 -1
  99. package/dist/ports.js +0 -688
  100. package/dist/ports.js.map +0 -1
  101. package/dist/prompt-doc.d.ts +0 -70
  102. package/dist/prompt-doc.d.ts.map +0 -1
  103. package/dist/prompt-doc.js +0 -154
  104. package/dist/prompt-doc.js.map +0 -1
  105. package/dist/registry.d.ts +0 -3
  106. package/dist/registry.d.ts.map +0 -1
  107. package/dist/registry.js +0 -2
  108. package/dist/registry.js.map +0 -1
  109. package/dist/task-ref.d.ts +0 -55
  110. package/dist/task-ref.d.ts.map +0 -1
  111. package/dist/task-ref.js +0 -103
  112. package/dist/task-ref.js.map +0 -1
  113. package/dist/utils.d.ts +0 -13
  114. package/dist/utils.d.ts.map +0 -1
  115. package/dist/utils.js +0 -177
  116. package/dist/utils.js.map +0 -1
  117. package/src/adapters/stdin-approval.ts +0 -1
  118. package/src/adapters/websocket-approval.ts +0 -1
  119. package/src/approval.ts +0 -9
  120. package/src/bootstrap.ts +0 -55
  121. package/src/completions/exit-code.ts +0 -34
  122. package/src/completions/file-exists.ts +0 -66
  123. package/src/completions/output-check.test.ts +0 -50
  124. package/src/completions/output-check.ts +0 -92
  125. package/src/config-ops.test.ts +0 -70
  126. package/src/config-ops.ts +0 -328
  127. package/src/config.ts +0 -26
  128. package/src/core/dataflow.test.ts +0 -166
  129. package/src/core/dataflow.ts +0 -161
  130. package/src/core/log-prune.test.ts +0 -58
  131. package/src/core/log-prune.ts +0 -43
  132. package/src/core/preflight.test.ts +0 -49
  133. package/src/core/preflight.ts +0 -89
  134. package/src/core/run-context.test.ts +0 -291
  135. package/src/core/run-context.ts +0 -211
  136. package/src/core/run-state.test.ts +0 -98
  137. package/src/core/run-state.ts +0 -122
  138. package/src/core/scheduler.test.ts +0 -83
  139. package/src/core/scheduler.ts +0 -42
  140. package/src/core/task-executor.ts +0 -752
  141. package/src/core/trigger-errors.ts +0 -15
  142. package/src/dag.test.ts +0 -56
  143. package/src/dag.ts +0 -245
  144. package/src/drivers/opencode.ts +0 -410
  145. package/src/engine-ports-mixed.test.ts +0 -182
  146. package/src/engine-ports.test.ts +0 -210
  147. package/src/engine-task-type.test.ts +0 -56
  148. package/src/engine.ts +0 -32
  149. package/src/hooks.ts +0 -193
  150. package/src/index.ts +0 -31
  151. package/src/logger.ts +0 -2
  152. package/src/middlewares/static-context.ts +0 -49
  153. package/src/package-split.test.ts +0 -15
  154. package/src/pipeline-definition.ts +0 -5
  155. package/src/pipeline-runner.test.ts +0 -144
  156. package/src/pipeline-runner.ts +0 -194
  157. package/src/plugin-registry.test.ts +0 -448
  158. package/src/plugins.ts +0 -21
  159. package/src/ports.test.ts +0 -678
  160. package/src/ports.ts +0 -925
  161. package/src/prompt-doc.test.ts +0 -174
  162. package/src/prompt-doc.ts +0 -169
  163. package/src/registry.ts +0 -7
  164. package/src/runner.test.ts +0 -142
  165. package/src/runner.ts +0 -1
  166. package/src/runtime/adapters/stdin-approval.ts +0 -1
  167. package/src/runtime/adapters/websocket-approval.ts +0 -1
  168. package/src/runtime/bun-process-runner.ts +0 -1
  169. package/src/runtime-adapters.test.ts +0 -10
  170. package/src/runtime.ts +0 -12
  171. package/src/schema-ports.test.ts +0 -172
  172. package/src/schema.test.ts +0 -213
  173. package/src/schema.ts +0 -379
  174. package/src/tagma.test.ts +0 -317
  175. package/src/tagma.ts +0 -67
  176. package/src/task-ref.test.ts +0 -401
  177. package/src/task-ref.ts +0 -121
  178. package/src/triggers/file.test.ts +0 -79
  179. package/src/triggers/file.ts +0 -131
  180. package/src/triggers/manual.ts +0 -86
  181. package/src/types.ts +0 -18
  182. package/src/utils-api.ts +0 -8
  183. package/src/utils.test.ts +0 -28
  184. package/src/utils.ts +0 -203
  185. package/src/validate-raw-plugin-types.test.ts +0 -60
  186. package/src/validate-raw-ports.test.ts +0 -136
  187. package/src/validate-raw.ts +0 -852
  188. package/src/yaml-compiler.test.ts +0 -108
  189. package/src/yaml-compiler.ts +0 -110
  190. package/src/yaml.ts +0 -11
package/src/config-ops.ts DELETED
@@ -1,328 +0,0 @@
1
- // ═══ RawPipelineConfig CRUD Operations ═══
2
- //
3
- // Pure, immutable helper functions for building and editing pipeline configs
4
- // in a visual editor. None of these functions have runtime dependencies —
5
- // safe to import in any context (sidecar, renderer, tests).
6
- //
7
- // All operations return a new config object; inputs are never mutated.
8
-
9
- import type { RawPipelineConfig, RawTrackConfig, RawTaskConfig } from './types';
10
-
11
- // ── Pipeline ──
12
-
13
- /**
14
- * Create a minimal empty pipeline config.
15
- */
16
- export function createEmptyPipeline(name: string): RawPipelineConfig {
17
- return { name, tracks: [] };
18
- }
19
-
20
- /**
21
- * Update a top-level pipeline field (name, driver, timeout, etc.).
22
- */
23
- export function setPipelineField(
24
- config: RawPipelineConfig,
25
- fields: Partial<Omit<RawPipelineConfig, 'tracks'>>,
26
- ): RawPipelineConfig {
27
- return { ...config, ...fields };
28
- }
29
-
30
- // ── Tracks ──
31
-
32
- /**
33
- * Insert or replace a track by id. Appends if the id is new.
34
- */
35
- export function upsertTrack(config: RawPipelineConfig, track: RawTrackConfig): RawPipelineConfig {
36
- const exists = config.tracks.some((t) => t.id === track.id);
37
- return {
38
- ...config,
39
- tracks: exists
40
- ? config.tracks.map((t) => (t.id === track.id ? track : t))
41
- : [...config.tracks, track],
42
- };
43
- }
44
-
45
- /**
46
- * Remove a track by id. No-op (same reference) if the id is not found.
47
- */
48
- export function removeTrack(config: RawPipelineConfig, trackId: string): RawPipelineConfig {
49
- if (!config.tracks.some((t) => t.id === trackId)) return config;
50
- return { ...config, tracks: config.tracks.filter((t) => t.id !== trackId) };
51
- }
52
-
53
- /**
54
- * Move a track to a new index position (0-based).
55
- * Clamps toIndex to valid bounds.
56
- */
57
- export function moveTrack(
58
- config: RawPipelineConfig,
59
- trackId: string,
60
- toIndex: number,
61
- ): RawPipelineConfig {
62
- const idx = config.tracks.findIndex((t) => t.id === trackId);
63
- if (idx === -1) return config;
64
- const track = config.tracks[idx]!;
65
- const withoutTrack = [...config.tracks.slice(0, idx), ...config.tracks.slice(idx + 1)];
66
- const clamped = Math.max(0, Math.min(toIndex, withoutTrack.length));
67
- const tracks = [...withoutTrack.slice(0, clamped), track, ...withoutTrack.slice(clamped)];
68
- return { ...config, tracks };
69
- }
70
-
71
- /**
72
- * Update fields on a single track (excluding tasks list, use upsertTask / removeTask for that).
73
- * No-op (same reference) if the trackId is not found.
74
- */
75
- export function updateTrack(
76
- config: RawPipelineConfig,
77
- trackId: string,
78
- fields: Partial<Omit<RawTrackConfig, 'id' | 'tasks'>>,
79
- ): RawPipelineConfig {
80
- if (!config.tracks.some((t) => t.id === trackId)) return config;
81
- return {
82
- ...config,
83
- tracks: config.tracks.map((t) => (t.id === trackId ? { ...t, ...fields } : t)),
84
- };
85
- }
86
-
87
- // ── Tasks ──
88
-
89
- /**
90
- * Insert or replace a task within a track, matched by task.id. Appends if new.
91
- * No-op (same reference) if the trackId is not found.
92
- */
93
- export function upsertTask(
94
- config: RawPipelineConfig,
95
- trackId: string,
96
- task: RawTaskConfig,
97
- ): RawPipelineConfig {
98
- if (!config.tracks.some((t) => t.id === trackId)) return config;
99
- return {
100
- ...config,
101
- tracks: config.tracks.map((t) => {
102
- if (t.id !== trackId) return t;
103
- const exists = t.tasks.some((tk) => tk.id === task.id);
104
- return {
105
- ...t,
106
- tasks: exists ? t.tasks.map((tk) => (tk.id === task.id ? task : tk)) : [...t.tasks, task],
107
- };
108
- }),
109
- };
110
- }
111
-
112
- /**
113
- * Remove a task from a track. No-op if either id is not found.
114
- *
115
- * When `cleanRefs` is true, all `depends_on` and `continue_from` references to the
116
- * removed task are also removed from every other task in the pipeline. This prevents
117
- * validateRaw from reporting dangling-ref errors after the deletion.
118
- */
119
- export function removeTask(
120
- config: RawPipelineConfig,
121
- trackId: string,
122
- taskId: string,
123
- cleanRefs = false,
124
- ): RawPipelineConfig {
125
- const track = config.tracks.find((t) => t.id === trackId);
126
- if (!track || !track.tasks.some((tk) => tk.id === taskId)) return config;
127
-
128
- const withoutTask = {
129
- ...config,
130
- tracks: config.tracks.map((t) => {
131
- if (t.id !== trackId) return t;
132
- return { ...t, tasks: t.tasks.filter((tk) => tk.id !== taskId) };
133
- }),
134
- };
135
-
136
- if (!cleanRefs) return withoutTask;
137
-
138
- const qualId = `${trackId}.${taskId}`;
139
-
140
- // After deletion, can a bare ref "taskId" still resolve to some other task globally?
141
- // It can if any track in the post-deletion config still contains a task with that bare id.
142
- const bareIdSurvivesGlobally = withoutTask.tracks.some((t) =>
143
- t.tasks.some((tk) => tk.id === taskId),
144
- );
145
-
146
- return {
147
- ...withoutTask,
148
- tracks: withoutTask.tracks.map((t) => {
149
- // Build the set of task IDs remaining in this track (the deleted task
150
- // has already been removed from its own track in withoutTask).
151
- const remainingIds = new Set(t.tasks.map((tk) => tk.id));
152
-
153
- // Resolve whether a ref in THIS track points to the deleted task:
154
- // - Fully-qualified ref ("trackId.taskId") — always points to the deleted task.
155
- // - Bare ref ("taskId") from the SAME track as the deleted task — always pointed
156
- // to the deleted task (same-track lookup takes priority).
157
- // - Bare ref from a DIFFERENT track:
158
- // 1. If this track has a local task with that id → ref resolves locally, not removed.
159
- // 2. Else if some other track still has a task with that id → ref will resolve
160
- // there after deletion, not removed.
161
- // 3. Else → ref is dangling, remove it.
162
- const isRemovedFrom = (ref: string): boolean => {
163
- if (ref === qualId) return true;
164
- if (ref === taskId) {
165
- if (t.id === trackId) return true; // same track — was pointing here
166
- if (remainingIds.has(taskId)) return false; // local task shadows — ref is fine
167
- return !bareIdSurvivesGlobally; // remove only if truly dangling
168
- }
169
- return false;
170
- };
171
-
172
- return {
173
- ...t,
174
- tasks: t.tasks.map((tk) => cleanTaskRefs(tk, isRemovedFrom)),
175
- };
176
- }),
177
- };
178
- }
179
-
180
- function cleanTaskRefs(task: RawTaskConfig, isRemoved: (ref: string) => boolean): RawTaskConfig {
181
- const filteredDeps = task.depends_on?.filter((d) => !isRemoved(d));
182
- const dropContinueFrom = task.continue_from !== undefined && isRemoved(task.continue_from);
183
-
184
- const depsUnchanged =
185
- filteredDeps === undefined || filteredDeps.length === task.depends_on!.length;
186
- if (depsUnchanged && !dropContinueFrom) return task;
187
-
188
- const { depends_on: _depends_on, continue_from, ...rest } = task;
189
- return {
190
- ...rest,
191
- ...(filteredDeps !== undefined && filteredDeps.length > 0 ? { depends_on: filteredDeps } : {}),
192
- ...(!dropContinueFrom && continue_from !== undefined ? { continue_from } : {}),
193
- } as RawTaskConfig;
194
- }
195
-
196
- /**
197
- * Reorder a task within its track.
198
- * Clamps toIndex to valid bounds.
199
- * No-op (same reference) if the trackId or taskId is not found.
200
- */
201
- export function moveTask(
202
- config: RawPipelineConfig,
203
- trackId: string,
204
- taskId: string,
205
- toIndex: number,
206
- ): RawPipelineConfig {
207
- const track = config.tracks.find((t) => t.id === trackId);
208
- if (!track || !track.tasks.some((tk) => tk.id === taskId)) return config;
209
- return {
210
- ...config,
211
- tracks: config.tracks.map((t) => {
212
- if (t.id !== trackId) return t;
213
- const idx = t.tasks.findIndex((tk) => tk.id === taskId);
214
- if (idx === -1) return t;
215
- const task = t.tasks[idx]!;
216
- const withoutTask = [...t.tasks.slice(0, idx), ...t.tasks.slice(idx + 1)];
217
- const clamped = Math.max(0, Math.min(toIndex, withoutTask.length));
218
- const tasks = [...withoutTask.slice(0, clamped), task, ...withoutTask.slice(clamped)];
219
- return { ...t, tasks };
220
- }),
221
- };
222
- }
223
-
224
- /**
225
- * Move a task from one track to another (appends to the target track).
226
- * No-op if either trackId or taskId is not found.
227
- *
228
- * When `qualifyRefs` is true (the default), bare references (`depends_on`,
229
- * `continue_from`) pointing to the moved task are converted to fully-qualified
230
- * refs (`toTrackId.taskId`) so that same-track resolution doesn't silently
231
- * break after the task changes tracks.
232
- */
233
- export function transferTask(
234
- config: RawPipelineConfig,
235
- fromTrackId: string,
236
- taskId: string,
237
- toTrackId: string,
238
- qualifyRefs = true,
239
- ): RawPipelineConfig {
240
- if (fromTrackId === toTrackId) return config;
241
- const targetTrack = config.tracks.find((t) => t.id === toTrackId);
242
- if (!targetTrack) return config;
243
- if (targetTrack.tasks.some((tk) => tk.id === taskId)) return config;
244
-
245
- let task: RawTaskConfig | undefined;
246
- let sourceLocalIds = new Set<string>();
247
- const afterRemove = {
248
- ...config,
249
- tracks: config.tracks.map((t) => {
250
- if (t.id !== fromTrackId) return t;
251
- const found = t.tasks.find((tk) => tk.id === taskId);
252
- if (!found) return t;
253
- sourceLocalIds = new Set(t.tasks.map((tk) => tk.id));
254
- task = found;
255
- return { ...t, tasks: t.tasks.filter((tk) => tk.id !== taskId) };
256
- }),
257
- };
258
- if (!task) return config;
259
- if (qualifyRefs) {
260
- task = qualifyTaskRefs(task, (ref) => {
261
- if (ref.includes('.') || !sourceLocalIds.has(ref)) return ref;
262
- return `${fromTrackId}.${ref}`;
263
- });
264
- }
265
- const afterInsert = upsertTask(afterRemove, toTrackId, task);
266
-
267
- if (!qualifyRefs) return afterInsert;
268
-
269
- // Qualify bare references to the moved task. After the move, bare ref
270
- // "taskId" from the old track no longer resolves via same-track priority.
271
- // Convert it to the qualified form "toTrackId.taskId" so the dependency
272
- // graph stays correct.
273
- const qualId = `${toTrackId}.${taskId}`;
274
- const oldQualId = `${fromTrackId}.${taskId}`;
275
-
276
- // Does any track (other than the destination) still have a task with this bare id?
277
- const bareIdSurvivesElsewhere = afterInsert.tracks.some(
278
- (t) => t.id !== toTrackId && t.tasks.some((tk) => tk.id === taskId),
279
- );
280
-
281
- return {
282
- ...afterInsert,
283
- tracks: afterInsert.tracks.map((t) => {
284
- const localHasId = t.tasks.some((tk) => tk.id === taskId);
285
-
286
- const qualifyRef = (ref: string): string => {
287
- // Already-qualified ref to old location → rewrite to new location
288
- if (ref === oldQualId) return qualId;
289
- // Bare ref: only needs qualifying if it would have resolved to the
290
- // moved task before the transfer
291
- if (ref === taskId) {
292
- if (t.id === fromTrackId) {
293
- // Was same-track in the old track — now the task is gone.
294
- // If no other local task shadows it, qualify to new location.
295
- if (!localHasId) return qualId;
296
- }
297
- // From a different track: bare ref resolved globally before.
298
- // If the bare id is now ambiguous or gone from this track's
299
- // perspective, qualify it.
300
- if (!localHasId && !bareIdSurvivesElsewhere) return qualId;
301
- }
302
- return ref;
303
- };
304
-
305
- return {
306
- ...t,
307
- tasks: t.tasks.map((tk) => qualifyTaskRefs(tk, qualifyRef)),
308
- };
309
- }),
310
- };
311
- }
312
-
313
- /** Rewrite `depends_on` and `continue_from` refs using a mapping function. */
314
- function qualifyTaskRefs(task: RawTaskConfig, rewrite: (ref: string) => string): RawTaskConfig {
315
- const newDeps = task.depends_on?.map(rewrite);
316
- const newContinue = task.continue_from !== undefined ? rewrite(task.continue_from) : undefined;
317
-
318
- const depsChanged = newDeps !== undefined && newDeps.some((d, i) => d !== task.depends_on![i]);
319
- const continueChanged = newContinue !== undefined && newContinue !== task.continue_from;
320
-
321
- if (!depsChanged && !continueChanged) return task;
322
-
323
- return {
324
- ...task,
325
- ...(newDeps !== undefined ? { depends_on: newDeps } : {}),
326
- ...(newContinue !== undefined ? { continue_from: newContinue } : {}),
327
- };
328
- }
package/src/config.ts DELETED
@@ -1,26 +0,0 @@
1
- export {
2
- createEmptyPipeline,
3
- setPipelineField,
4
- upsertTrack,
5
- removeTrack,
6
- moveTrack,
7
- updateTrack,
8
- upsertTask,
9
- removeTask,
10
- moveTask,
11
- transferTask,
12
- } from './config-ops';
13
- export { validateRaw } from './validate-raw';
14
- export type { ValidationError, KnownPluginTypes } from './validate-raw';
15
- export { buildRawDag } from './dag';
16
- export type { RawDag, RawDagNode } from './dag';
17
- export {
18
- TASK_ID_RE,
19
- isValidTaskId,
20
- qualifyTaskId,
21
- isQualifiedRef,
22
- buildTaskIndex,
23
- resolveTaskRef,
24
- AMBIGUOUS,
25
- } from './task-ref';
26
- export type { TaskIndex, RefResolution } from './task-ref';
@@ -1,166 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { buildDag } from '../dag';
3
- import type { PipelineConfig, TaskResult } from '../types';
4
- import { RunContext } from './run-context';
5
- import {
6
- inferEffectivePorts,
7
- extractSuccessfulOutputs,
8
- } from './dataflow';
9
-
10
- function makeContext(config: PipelineConfig): RunContext {
11
- return new RunContext({
12
- runId: 'run_dataflow',
13
- dag: buildDag(config),
14
- config,
15
- workDir: '/tmp/wd',
16
- pipelineInfo: {
17
- name: config.name,
18
- run_id: 'run_dataflow',
19
- started_at: '2026-04-26T00:00:00Z',
20
- },
21
- });
22
- }
23
-
24
- function result(stdout: string, normalizedOutput: string | null = null): TaskResult {
25
- return {
26
- exitCode: 0,
27
- stdout,
28
- stderr: '',
29
- stdoutPath: null,
30
- stderrPath: null,
31
- durationMs: 1,
32
- sessionId: null,
33
- normalizedOutput,
34
- failureKind: null,
35
- };
36
- }
37
-
38
- describe('inferEffectivePorts', () => {
39
- test('returns typed outputs for command tasks', () => {
40
- const config: PipelineConfig = {
41
- name: 'p',
42
- tracks: [
43
- {
44
- id: 't',
45
- name: 'T',
46
- tasks: [
47
- {
48
- id: 'cmd',
49
- name: 'Cmd',
50
- command: 'echo',
51
- outputs: { city: { type: 'string' } },
52
- },
53
- ],
54
- },
55
- ],
56
- };
57
- const ctx = makeContext(config);
58
- const inferred = inferEffectivePorts(ctx, 't.cmd');
59
- expect(inferred.kind).toBe('ready');
60
- if (inferred.kind === 'ready') {
61
- expect(inferred.isPromptTask).toBe(false);
62
- expect(inferred.effectivePorts?.outputs?.[0]?.name).toBe('city');
63
- }
64
- });
65
-
66
- test('infers prompt inputs from upstream command outputs', () => {
67
- const config: PipelineConfig = {
68
- name: 'p',
69
- tracks: [
70
- {
71
- id: 't',
72
- name: 'T',
73
- tasks: [
74
- {
75
- id: 'up',
76
- name: 'Up',
77
- command: 'echo',
78
- outputs: { city: { type: 'string' } },
79
- },
80
- { id: 'prompt', name: 'Prompt', prompt: 'hi', depends_on: ['up'] },
81
- ],
82
- },
83
- ],
84
- };
85
- const ctx = makeContext(config);
86
- const inferred = inferEffectivePorts(ctx, 't.prompt');
87
- expect(inferred.kind).toBe('ready');
88
- if (inferred.kind === 'ready') {
89
- expect(inferred.isPromptTask).toBe(true);
90
- expect(inferred.effectivePorts?.inputs?.[0]?.name).toBe('city');
91
- }
92
- });
93
-
94
- test('blocks prompt tasks when upstream command outputs are ambiguous', () => {
95
- const config: PipelineConfig = {
96
- name: 'p',
97
- tracks: [
98
- {
99
- id: 't',
100
- name: 'T',
101
- tasks: [
102
- {
103
- id: 'a',
104
- name: 'A',
105
- command: 'echo',
106
- outputs: { city: { type: 'string' } },
107
- },
108
- {
109
- id: 'b',
110
- name: 'B',
111
- command: 'echo',
112
- outputs: { city: { type: 'string' } },
113
- },
114
- {
115
- id: 'prompt',
116
- name: 'Prompt',
117
- prompt: 'hi',
118
- depends_on: ['a', 'b'],
119
- },
120
- ],
121
- },
122
- ],
123
- };
124
- const ctx = makeContext(config);
125
- const inferred = inferEffectivePorts(ctx, 't.prompt');
126
- expect(inferred.kind).toBe('blocked');
127
- if (inferred.kind === 'blocked') {
128
- expect(inferred.reason).toContain('city');
129
- }
130
- });
131
- });
132
-
133
- describe('extractSuccessfulOutputs', () => {
134
- test('extracts typed binding outputs', () => {
135
- const config: PipelineConfig = {
136
- name: 'p',
137
- tracks: [
138
- {
139
- id: 't',
140
- name: 'T',
141
- tasks: [
142
- {
143
- id: 'cmd',
144
- name: 'Cmd',
145
- command: 'echo',
146
- outputs: { city: { type: 'string' }, raw: { from: 'stdout' } },
147
- },
148
- ],
149
- },
150
- ],
151
- };
152
- const ctx = makeContext(config);
153
- const node = ctx.dag.nodes.get('t.cmd')!;
154
- const extracted = extractSuccessfulOutputs({
155
- task: node.task,
156
- effectivePorts: undefined,
157
- result: result('{"city":"Paris"}'),
158
- });
159
- expect(extracted.outputs).toEqual({
160
- raw: '{"city":"Paris"}',
161
- city: 'Paris',
162
- });
163
- expect(extracted.bindingDiagnostic).toBeNull();
164
- expect(extracted.portDiagnostic).toBeNull();
165
- });
166
- });
@@ -1,161 +0,0 @@
1
- import type {
2
- PortDef,
3
- TaskConfig,
4
- TaskInputBindings,
5
- TaskOutputBindings,
6
- TaskPorts,
7
- TaskResult,
8
- } from '../types';
9
- import {
10
- extractTaskBindingOutputs,
11
- extractTaskOutputs,
12
- inferPromptPorts,
13
- } from '../ports';
14
- import type { RunContext } from './run-context';
15
-
16
- function isPromptTaskConfig(
17
- task: TaskConfig,
18
- ): task is TaskConfig & { readonly prompt: string; readonly command?: undefined } {
19
- return task.prompt !== undefined && task.command === undefined;
20
- }
21
-
22
- function isCommandTaskConfig(
23
- task: TaskConfig,
24
- ): task is TaskConfig & { readonly command: string; readonly prompt?: undefined } {
25
- return task.command !== undefined && task.prompt === undefined;
26
- }
27
-
28
- function inputBindingsToPorts(bindings: TaskInputBindings | undefined): PortDef[] | undefined {
29
- if (!bindings || Object.keys(bindings).length === 0) return undefined;
30
- return Object.entries(bindings).map(([name, binding]) => ({
31
- name,
32
- type: binding.type ?? 'json',
33
- ...(binding.description ? { description: binding.description } : {}),
34
- ...(binding.required !== undefined ? { required: binding.required } : {}),
35
- ...(binding.default !== undefined ? { default: binding.default } : {}),
36
- ...(binding.enum ? { enum: [...binding.enum] } : {}),
37
- ...(binding.from ? { from: binding.from } : {}),
38
- }));
39
- }
40
-
41
- function outputBindingsToPorts(bindings: TaskOutputBindings | undefined): PortDef[] | undefined {
42
- if (!bindings || Object.keys(bindings).length === 0) return undefined;
43
- return Object.entries(bindings).map(([name, binding]) => ({
44
- name,
45
- type: binding.type ?? 'json',
46
- ...(binding.description ? { description: binding.description } : {}),
47
- ...(binding.default !== undefined ? { default: binding.default } : {}),
48
- ...(binding.enum ? { enum: [...binding.enum] } : {}),
49
- }));
50
- }
51
-
52
- function taskBindingsAsPorts(task: TaskConfig): TaskPorts | undefined {
53
- const inputs = inputBindingsToPorts(task.inputs);
54
- const outputs = outputBindingsToPorts(task.outputs);
55
- if (!inputs && !outputs) return undefined;
56
- return {
57
- ...(inputs ? { inputs } : {}),
58
- ...(outputs ? { outputs } : {}),
59
- };
60
- }
61
-
62
- export type EffectivePortsResult =
63
- | {
64
- readonly kind: 'ready';
65
- readonly isPromptTask: boolean;
66
- readonly effectivePorts: TaskPorts | undefined;
67
- }
68
- | {
69
- readonly kind: 'blocked';
70
- readonly reason: string;
71
- };
72
-
73
- export function inferEffectivePorts(
74
- ctx: RunContext,
75
- taskId: string,
76
- ): EffectivePortsResult {
77
- const node = ctx.dag.nodes.get(taskId)!;
78
- const task = node.task;
79
- const isPromptTask = isPromptTaskConfig(task);
80
-
81
- if (!isPromptTask) {
82
- return { kind: 'ready', isPromptTask: false, effectivePorts: taskBindingsAsPorts(task) };
83
- }
84
-
85
- const inference = inferPromptPorts({
86
- upstreams: node.dependsOn.map((upstreamId) => {
87
- const upstream = ctx.dag.nodes.get(upstreamId);
88
- const isUpstreamCommand = upstream ? isCommandTaskConfig(upstream.task) : false;
89
- return {
90
- taskId: upstreamId,
91
- outputs: isUpstreamCommand ? outputBindingsToPorts(upstream?.task.outputs) : undefined,
92
- };
93
- }),
94
- downstreams: (ctx.directDownstreams.get(taskId) ?? []).map((downstreamId) => {
95
- const downstream = ctx.dag.nodes.get(downstreamId);
96
- const isDownstreamCommand = downstream ? isCommandTaskConfig(downstream.task) : false;
97
- return {
98
- taskId: downstreamId,
99
- inputs: isDownstreamCommand ? inputBindingsToPorts(downstream?.task.inputs) : undefined,
100
- };
101
- }),
102
- });
103
-
104
- if (inference.inputConflicts.length > 0 || inference.outputConflicts.length > 0) {
105
- const lines: string[] = [];
106
- for (const conflict of inference.inputConflicts) lines.push(conflict.reason);
107
- for (const conflict of inference.outputConflicts) lines.push(conflict.reason);
108
- return { kind: 'blocked', reason: lines.join('\n') };
109
- }
110
-
111
- return { kind: 'ready', isPromptTask: true, effectivePorts: inference.ports };
112
- }
113
-
114
- export interface ExtractSuccessfulOutputsOptions {
115
- readonly task: TaskConfig;
116
- readonly effectivePorts: TaskPorts | undefined;
117
- readonly result: TaskResult;
118
- }
119
-
120
- export interface ExtractSuccessfulOutputsResult {
121
- readonly outputs: Readonly<Record<string, unknown>> | null;
122
- readonly bindingDiagnostic: string | null;
123
- readonly portDiagnostic: string | null;
124
- }
125
-
126
- export function extractSuccessfulOutputs(
127
- options: ExtractSuccessfulOutputsOptions,
128
- ): ExtractSuccessfulOutputsResult {
129
- const { task, effectivePorts, result } = options;
130
- let extractedOutputs: Readonly<Record<string, unknown>> | null = null;
131
-
132
- const bindingExtraction = extractTaskBindingOutputs(
133
- task.outputs,
134
- result.stdout,
135
- result.stderr,
136
- result.normalizedOutput,
137
- );
138
- if (task.outputs && Object.keys(task.outputs).length > 0) {
139
- extractedOutputs = bindingExtraction.outputs;
140
- }
141
-
142
- if ((!task.outputs || Object.keys(task.outputs).length === 0) && effectivePorts?.outputs?.length) {
143
- const portExtraction = extractTaskOutputs(
144
- effectivePorts,
145
- result.stdout,
146
- result.normalizedOutput,
147
- );
148
- extractedOutputs = portExtraction.outputs;
149
- return {
150
- outputs: extractedOutputs,
151
- bindingDiagnostic: bindingExtraction.diagnostic,
152
- portDiagnostic: portExtraction.diagnostic,
153
- };
154
- }
155
-
156
- return {
157
- outputs: extractedOutputs,
158
- bindingDiagnostic: bindingExtraction.diagnostic,
159
- portDiagnostic: null,
160
- };
161
- }