@tagma/sdk 0.7.3 → 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 (230) hide show
  1. package/README.md +85 -57
  2. package/dist/approval.d.ts +2 -12
  3. package/dist/approval.d.ts.map +1 -1
  4. package/dist/approval.js +1 -90
  5. package/dist/approval.js.map +1 -1
  6. package/dist/bootstrap.d.ts +1 -1
  7. package/dist/bootstrap.d.ts.map +1 -1
  8. package/dist/completions/file-exists.js +1 -1
  9. package/dist/completions/file-exists.js.map +1 -1
  10. package/dist/completions/output-check.d.ts.map +1 -1
  11. package/dist/completions/output-check.js +17 -4
  12. package/dist/completions/output-check.js.map +1 -1
  13. package/dist/config.d.ts +4 -4
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +2 -2
  16. package/dist/config.js.map +1 -1
  17. package/dist/dataflow.d.ts +3 -0
  18. package/dist/dataflow.d.ts.map +1 -0
  19. package/dist/dataflow.js +2 -0
  20. package/dist/dataflow.js.map +1 -0
  21. package/dist/drivers/opencode.d.ts.map +1 -1
  22. package/dist/drivers/opencode.js +23 -71
  23. package/dist/drivers/opencode.js.map +1 -1
  24. package/dist/engine.d.ts +5 -56
  25. package/dist/engine.d.ts.map +1 -1
  26. package/dist/engine.js +7 -297
  27. package/dist/engine.js.map +1 -1
  28. package/dist/index.d.ts +4 -6
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/logger.d.ts +2 -60
  33. package/dist/logger.d.ts.map +1 -1
  34. package/dist/logger.js +1 -153
  35. package/dist/logger.js.map +1 -1
  36. package/dist/middlewares/static-context.d.ts.map +1 -1
  37. package/dist/middlewares/static-context.js +1 -2
  38. package/dist/middlewares/static-context.js.map +1 -1
  39. package/dist/pipeline-runner.d.ts.map +1 -1
  40. package/dist/pipeline-runner.js +2 -2
  41. package/dist/pipeline-runner.js.map +1 -1
  42. package/dist/plugins.d.ts +2 -2
  43. package/dist/plugins.d.ts.map +1 -1
  44. package/dist/plugins.js +1 -1
  45. package/dist/plugins.js.map +1 -1
  46. package/dist/runner.d.ts +1 -35
  47. package/dist/runner.d.ts.map +1 -1
  48. package/dist/runner.js +1 -610
  49. package/dist/runner.js.map +1 -1
  50. package/dist/runtime/adapters/stdin-approval.d.ts +2 -0
  51. package/dist/runtime/adapters/stdin-approval.d.ts.map +1 -0
  52. package/dist/runtime/adapters/stdin-approval.js +2 -0
  53. package/dist/runtime/adapters/stdin-approval.js.map +1 -0
  54. package/dist/runtime/adapters/websocket-approval.d.ts +2 -0
  55. package/dist/runtime/adapters/websocket-approval.d.ts.map +1 -0
  56. package/dist/runtime/adapters/websocket-approval.js +2 -0
  57. package/dist/runtime/adapters/websocket-approval.js.map +1 -0
  58. package/dist/runtime/bun-process-runner.d.ts +2 -0
  59. package/dist/runtime/bun-process-runner.d.ts.map +1 -0
  60. package/dist/runtime/bun-process-runner.js +2 -0
  61. package/dist/runtime/bun-process-runner.js.map +1 -0
  62. package/dist/runtime.d.ts +2 -8
  63. package/dist/runtime.d.ts.map +1 -1
  64. package/dist/runtime.js +1 -7
  65. package/dist/runtime.js.map +1 -1
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +3 -4
  68. package/dist/schema.js.map +1 -1
  69. package/dist/tagma.d.ts +3 -4
  70. package/dist/tagma.d.ts.map +1 -1
  71. package/dist/tagma.js +2 -3
  72. package/dist/tagma.js.map +1 -1
  73. package/dist/triggers/file.d.ts.map +1 -1
  74. package/dist/triggers/file.js +74 -108
  75. package/dist/triggers/file.js.map +1 -1
  76. package/dist/triggers/manual.d.ts.map +1 -1
  77. package/dist/triggers/manual.js +1 -2
  78. package/dist/triggers/manual.js.map +1 -1
  79. package/dist/types.d.ts +1 -2
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js +1 -12
  82. package/dist/types.js.map +1 -1
  83. package/dist/utils-api.d.ts +1 -1
  84. package/dist/utils-api.d.ts.map +1 -1
  85. package/dist/utils-api.js +1 -1
  86. package/dist/utils-api.js.map +1 -1
  87. package/dist/validate-raw.d.ts.map +1 -1
  88. package/dist/validate-raw.js +5 -12
  89. package/dist/validate-raw.js.map +1 -1
  90. package/package.json +20 -22
  91. package/dist/adapters/stdin-approval.d.ts +0 -6
  92. package/dist/adapters/stdin-approval.d.ts.map +0 -1
  93. package/dist/adapters/stdin-approval.js +0 -90
  94. package/dist/adapters/stdin-approval.js.map +0 -1
  95. package/dist/adapters/websocket-approval.d.ts +0 -28
  96. package/dist/adapters/websocket-approval.d.ts.map +0 -1
  97. package/dist/adapters/websocket-approval.js +0 -147
  98. package/dist/adapters/websocket-approval.js.map +0 -1
  99. package/dist/core/dataflow.d.ts +0 -23
  100. package/dist/core/dataflow.d.ts.map +0 -1
  101. package/dist/core/dataflow.js +0 -99
  102. package/dist/core/dataflow.js.map +0 -1
  103. package/dist/core/log-prune.d.ts +0 -16
  104. package/dist/core/log-prune.d.ts.map +0 -1
  105. package/dist/core/log-prune.js +0 -34
  106. package/dist/core/log-prune.js.map +0 -1
  107. package/dist/core/preflight.d.ts +0 -13
  108. package/dist/core/preflight.d.ts.map +0 -1
  109. package/dist/core/preflight.js +0 -61
  110. package/dist/core/preflight.js.map +0 -1
  111. package/dist/core/run-context.d.ts +0 -55
  112. package/dist/core/run-context.d.ts.map +0 -1
  113. package/dist/core/run-context.js +0 -158
  114. package/dist/core/run-context.js.map +0 -1
  115. package/dist/core/run-state.d.ts +0 -25
  116. package/dist/core/run-state.d.ts.map +0 -1
  117. package/dist/core/run-state.js +0 -93
  118. package/dist/core/run-state.js.map +0 -1
  119. package/dist/core/scheduler.d.ts +0 -13
  120. package/dist/core/scheduler.d.ts.map +0 -1
  121. package/dist/core/scheduler.js +0 -35
  122. package/dist/core/scheduler.js.map +0 -1
  123. package/dist/core/task-executor.d.ts +0 -13
  124. package/dist/core/task-executor.d.ts.map +0 -1
  125. package/dist/core/task-executor.js +0 -601
  126. package/dist/core/task-executor.js.map +0 -1
  127. package/dist/core/trigger-errors.d.ts +0 -9
  128. package/dist/core/trigger-errors.d.ts.map +0 -1
  129. package/dist/core/trigger-errors.js +0 -15
  130. package/dist/core/trigger-errors.js.map +0 -1
  131. package/dist/dag.d.ts +0 -45
  132. package/dist/dag.d.ts.map +0 -1
  133. package/dist/dag.js +0 -177
  134. package/dist/dag.js.map +0 -1
  135. package/dist/hooks.d.ts +0 -73
  136. package/dist/hooks.d.ts.map +0 -1
  137. package/dist/hooks.js +0 -106
  138. package/dist/hooks.js.map +0 -1
  139. package/dist/pipeline-definition.d.ts +0 -3
  140. package/dist/pipeline-definition.d.ts.map +0 -1
  141. package/dist/pipeline-definition.js +0 -4
  142. package/dist/pipeline-definition.js.map +0 -1
  143. package/dist/ports.d.ts +0 -196
  144. package/dist/ports.d.ts.map +0 -1
  145. package/dist/ports.js +0 -688
  146. package/dist/ports.js.map +0 -1
  147. package/dist/prompt-doc.d.ts +0 -70
  148. package/dist/prompt-doc.d.ts.map +0 -1
  149. package/dist/prompt-doc.js +0 -154
  150. package/dist/prompt-doc.js.map +0 -1
  151. package/dist/registry.d.ts +0 -67
  152. package/dist/registry.d.ts.map +0 -1
  153. package/dist/registry.js +0 -293
  154. package/dist/registry.js.map +0 -1
  155. package/dist/task-ref.d.ts +0 -55
  156. package/dist/task-ref.d.ts.map +0 -1
  157. package/dist/task-ref.js +0 -103
  158. package/dist/task-ref.js.map +0 -1
  159. package/dist/utils.d.ts +0 -13
  160. package/dist/utils.d.ts.map +0 -1
  161. package/dist/utils.js +0 -177
  162. package/dist/utils.js.map +0 -1
  163. package/src/adapters/stdin-approval.ts +0 -106
  164. package/src/adapters/websocket-approval.ts +0 -224
  165. package/src/approval.ts +0 -131
  166. package/src/bootstrap.ts +0 -55
  167. package/src/completions/exit-code.ts +0 -34
  168. package/src/completions/file-exists.ts +0 -66
  169. package/src/completions/output-check.test.ts +0 -50
  170. package/src/completions/output-check.ts +0 -92
  171. package/src/config-ops.test.ts +0 -70
  172. package/src/config-ops.ts +0 -328
  173. package/src/config.ts +0 -26
  174. package/src/core/dataflow.test.ts +0 -166
  175. package/src/core/dataflow.ts +0 -161
  176. package/src/core/log-prune.test.ts +0 -58
  177. package/src/core/log-prune.ts +0 -43
  178. package/src/core/preflight.test.ts +0 -49
  179. package/src/core/preflight.ts +0 -89
  180. package/src/core/run-context.test.ts +0 -256
  181. package/src/core/run-context.ts +0 -211
  182. package/src/core/run-state.test.ts +0 -98
  183. package/src/core/run-state.ts +0 -122
  184. package/src/core/scheduler.test.ts +0 -83
  185. package/src/core/scheduler.ts +0 -42
  186. package/src/core/task-executor.ts +0 -743
  187. package/src/core/trigger-errors.ts +0 -15
  188. package/src/dag.test.ts +0 -56
  189. package/src/dag.ts +0 -245
  190. package/src/drivers/opencode.ts +0 -410
  191. package/src/engine-ports-mixed.test.ts +0 -156
  192. package/src/engine-ports.test.ts +0 -166
  193. package/src/engine-task-type.test.ts +0 -56
  194. package/src/engine.ts +0 -458
  195. package/src/hooks.ts +0 -193
  196. package/src/index.ts +0 -33
  197. package/src/logger.ts +0 -182
  198. package/src/middlewares/static-context.ts +0 -49
  199. package/src/pipeline-definition.ts +0 -5
  200. package/src/pipeline-runner.test.ts +0 -91
  201. package/src/pipeline-runner.ts +0 -194
  202. package/src/plugin-registry.test.ts +0 -382
  203. package/src/plugins.ts +0 -21
  204. package/src/ports.test.ts +0 -678
  205. package/src/ports.ts +0 -925
  206. package/src/prompt-doc.test.ts +0 -174
  207. package/src/prompt-doc.ts +0 -169
  208. package/src/registry.ts +0 -353
  209. package/src/runner.test.ts +0 -142
  210. package/src/runner.ts +0 -666
  211. package/src/runtime.ts +0 -20
  212. package/src/schema-ports.test.ts +0 -172
  213. package/src/schema.test.ts +0 -213
  214. package/src/schema.ts +0 -379
  215. package/src/tagma.test.ts +0 -155
  216. package/src/tagma.ts +0 -62
  217. package/src/task-ref.test.ts +0 -401
  218. package/src/task-ref.ts +0 -121
  219. package/src/triggers/file.ts +0 -164
  220. package/src/triggers/manual.ts +0 -86
  221. package/src/types.ts +0 -18
  222. package/src/utils-api.ts +0 -8
  223. package/src/utils.test.ts +0 -28
  224. package/src/utils.ts +0 -203
  225. package/src/validate-raw-plugin-types.test.ts +0 -60
  226. package/src/validate-raw-ports.test.ts +0 -136
  227. package/src/validate-raw.ts +0 -852
  228. package/src/yaml-compiler.test.ts +0 -108
  229. package/src/yaml-compiler.ts +0 -110
  230. package/src/yaml.ts +0 -11
@@ -1,70 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { transferTask } from './config-ops';
3
- import type { RawPipelineConfig } from './types';
4
-
5
- describe('transferTask', () => {
6
- test('does not remove the source task when the target track is missing', () => {
7
- const config: RawPipelineConfig = {
8
- name: 'Transfer',
9
- tracks: [
10
- {
11
- id: 'a',
12
- name: 'A',
13
- tasks: [{ id: 'move_me', command: 'echo a' }],
14
- },
15
- ],
16
- };
17
-
18
- const next = transferTask(config, 'a', 'move_me', 'missing');
19
-
20
- expect(next).toEqual(config);
21
- });
22
-
23
- test('qualifies moved task same-track dependencies so they keep pointing at the old track', () => {
24
- const config: RawPipelineConfig = {
25
- name: 'Transfer',
26
- tracks: [
27
- {
28
- id: 'a',
29
- name: 'A',
30
- tasks: [
31
- { id: 'build', command: 'echo old' },
32
- { id: 'move_me', command: 'echo move', depends_on: ['build'] },
33
- ],
34
- },
35
- {
36
- id: 'b',
37
- name: 'B',
38
- tasks: [{ id: 'build', command: 'echo new' }],
39
- },
40
- ],
41
- };
42
-
43
- const next = transferTask(config, 'a', 'move_me', 'b');
44
- const moved = next.tracks[1].tasks.find((task) => task.id === 'move_me');
45
-
46
- expect(moved?.depends_on).toEqual(['a.build']);
47
- });
48
-
49
- test('does not overwrite an existing task in the target track with the same id', () => {
50
- const config: RawPipelineConfig = {
51
- name: 'Transfer',
52
- tracks: [
53
- {
54
- id: 'a',
55
- name: 'A',
56
- tasks: [{ id: 'same', command: 'echo source' }],
57
- },
58
- {
59
- id: 'b',
60
- name: 'B',
61
- tasks: [{ id: 'same', command: 'echo target' }],
62
- },
63
- ],
64
- };
65
-
66
- const next = transferTask(config, 'a', 'same', 'b');
67
-
68
- expect(next).toEqual(config);
69
- });
70
- });
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
- });