@tagma/sdk 0.6.12 → 0.7.1

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 (125) hide show
  1. package/README.md +56 -15
  2. package/dist/bootstrap.d.ts +6 -6
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +5 -6
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/config.d.ts +8 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +5 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/dataflow.d.ts +23 -0
  11. package/dist/core/dataflow.d.ts.map +1 -0
  12. package/dist/core/dataflow.js +99 -0
  13. package/dist/core/dataflow.js.map +1 -0
  14. package/dist/core/log-prune.d.ts +16 -0
  15. package/dist/core/log-prune.d.ts.map +1 -0
  16. package/dist/core/log-prune.js +34 -0
  17. package/dist/core/log-prune.js.map +1 -0
  18. package/dist/core/preflight.d.ts +13 -0
  19. package/dist/core/preflight.d.ts.map +1 -0
  20. package/dist/core/preflight.js +61 -0
  21. package/dist/core/preflight.js.map +1 -0
  22. package/dist/core/run-context.d.ts +52 -0
  23. package/dist/core/run-context.d.ts.map +1 -0
  24. package/dist/core/run-context.js +156 -0
  25. package/dist/core/run-context.js.map +1 -0
  26. package/dist/core/run-state.d.ts +25 -0
  27. package/dist/core/run-state.d.ts.map +1 -0
  28. package/dist/core/run-state.js +93 -0
  29. package/dist/core/run-state.js.map +1 -0
  30. package/dist/core/scheduler.d.ts +13 -0
  31. package/dist/core/scheduler.d.ts.map +1 -0
  32. package/dist/core/scheduler.js +35 -0
  33. package/dist/core/scheduler.js.map +1 -0
  34. package/dist/core/task-executor.d.ts +13 -0
  35. package/dist/core/task-executor.d.ts.map +1 -0
  36. package/dist/core/task-executor.js +623 -0
  37. package/dist/core/task-executor.js.map +1 -0
  38. package/dist/core/trigger-errors.d.ts +9 -0
  39. package/dist/core/trigger-errors.d.ts.map +1 -0
  40. package/dist/core/trigger-errors.js +15 -0
  41. package/dist/core/trigger-errors.js.map +1 -0
  42. package/dist/engine.d.ts +6 -14
  43. package/dist/engine.d.ts.map +1 -1
  44. package/dist/engine.js +68 -1035
  45. package/dist/engine.js.map +1 -1
  46. package/dist/index.d.ts +9 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +6 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/pipeline-definition.d.ts +3 -0
  51. package/dist/pipeline-definition.d.ts.map +1 -0
  52. package/dist/pipeline-definition.js +4 -0
  53. package/dist/pipeline-definition.js.map +1 -0
  54. package/dist/pipeline-runner.d.ts +2 -1
  55. package/dist/pipeline-runner.d.ts.map +1 -1
  56. package/dist/pipeline-runner.js +2 -2
  57. package/dist/pipeline-runner.js.map +1 -1
  58. package/dist/plugins.d.ts +5 -0
  59. package/dist/plugins.d.ts.map +1 -0
  60. package/dist/plugins.js +3 -0
  61. package/dist/plugins.js.map +1 -0
  62. package/dist/ports.d.ts +4 -0
  63. package/dist/ports.d.ts.map +1 -1
  64. package/dist/ports.js +27 -4
  65. package/dist/ports.js.map +1 -1
  66. package/dist/registry.d.ts +3 -19
  67. package/dist/registry.d.ts.map +1 -1
  68. package/dist/registry.js +7 -35
  69. package/dist/registry.js.map +1 -1
  70. package/dist/tagma.d.ts +24 -0
  71. package/dist/tagma.d.ts.map +1 -0
  72. package/dist/tagma.js +23 -0
  73. package/dist/tagma.js.map +1 -0
  74. package/dist/utils-api.d.ts +2 -0
  75. package/dist/utils-api.d.ts.map +1 -0
  76. package/dist/utils-api.js +2 -0
  77. package/dist/utils-api.js.map +1 -0
  78. package/dist/validate-raw.d.ts +4 -4
  79. package/dist/validate-raw.js +91 -132
  80. package/dist/validate-raw.js.map +1 -1
  81. package/dist/yaml.d.ts +4 -0
  82. package/dist/yaml.d.ts.map +1 -0
  83. package/dist/yaml.js +3 -0
  84. package/dist/yaml.js.map +1 -0
  85. package/package.json +53 -8
  86. package/src/bootstrap.ts +6 -6
  87. package/src/config.ts +26 -0
  88. package/src/core/dataflow.test.ts +166 -0
  89. package/src/core/dataflow.ts +161 -0
  90. package/src/core/log-prune.test.ts +58 -0
  91. package/src/core/log-prune.ts +43 -0
  92. package/src/core/preflight.test.ts +49 -0
  93. package/src/core/preflight.ts +89 -0
  94. package/src/core/run-context.test.ts +244 -0
  95. package/src/core/run-context.ts +207 -0
  96. package/src/core/run-state.test.ts +98 -0
  97. package/src/core/run-state.ts +122 -0
  98. package/src/core/scheduler.test.ts +83 -0
  99. package/src/core/scheduler.ts +42 -0
  100. package/src/core/task-executor.ts +769 -0
  101. package/src/core/trigger-errors.ts +15 -0
  102. package/src/engine-ports-mixed.test.ts +68 -411
  103. package/src/engine-ports.test.ts +37 -341
  104. package/src/engine.ts +80 -1248
  105. package/src/index.ts +28 -0
  106. package/src/pipeline-definition.ts +5 -0
  107. package/src/pipeline-runner.test.ts +5 -9
  108. package/src/pipeline-runner.ts +3 -2
  109. package/src/plugin-registry.test.ts +7 -10
  110. package/src/plugins.ts +18 -0
  111. package/src/ports.test.ts +80 -0
  112. package/src/ports.ts +36 -4
  113. package/src/registry.ts +7 -49
  114. package/src/schema-ports.test.ts +41 -214
  115. package/src/tagma.test.ts +84 -0
  116. package/src/tagma.ts +47 -0
  117. package/src/utils-api.ts +8 -0
  118. package/src/validate-raw-ports.test.ts +80 -393
  119. package/src/validate-raw.ts +93 -137
  120. package/src/yaml.ts +11 -0
  121. package/dist/sdk.d.ts +0 -32
  122. package/dist/sdk.d.ts.map +0 -1
  123. package/dist/sdk.js +0 -41
  124. package/dist/sdk.js.map +0 -1
  125. package/src/sdk.ts +0 -151
@@ -1,19 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import yaml from 'js-yaml';
3
3
  import type { PipelineConfig, RawPipelineConfig } from './types';
4
- import {
5
- deresolvePipeline,
6
- parseYaml,
7
- resolveConfig,
8
- serializePipeline,
9
- } from './schema';
4
+ import { deresolvePipeline, parseYaml, resolveConfig, serializePipeline } from './schema';
10
5
 
11
6
  const WORK_DIR = process.platform === 'win32' ? 'D:\\fake-work' : '/fake-work';
12
7
 
13
- // ─── resolveConfig preserves ports ───────────────────────────────────
14
-
15
- describe('resolveConfig — ports passthrough', () => {
16
- test('raw lightweight bindings survive onto the resolved task', () => {
8
+ describe('schema unified bindings passthrough', () => {
9
+ test('typed inputs and outputs survive onto the resolved task', () => {
17
10
  const raw: RawPipelineConfig = {
18
11
  name: 'p',
19
12
  tracks: [
@@ -24,94 +17,19 @@ describe('resolveConfig — ports passthrough', () => {
24
17
  {
25
18
  id: 'a',
26
19
  command: 'echo "{{inputs.city}}"',
27
- inputs: {
28
- city: { from: 't.plan.outputs.city', required: true },
29
- },
30
- outputs: {
31
- report: { from: 'json.reportPath' },
32
- },
20
+ inputs: { city: { from: 't.plan.outputs.city', type: 'string', required: true } },
21
+ outputs: { report: { from: 'json.reportPath', type: 'string' } },
33
22
  },
34
23
  ],
35
24
  },
36
25
  ],
37
26
  };
38
- const resolved = resolveConfig(raw, WORK_DIR);
39
- const task = resolved.tracks[0]!.tasks[0]!;
27
+ const task = resolveConfig(raw, WORK_DIR).tracks[0]!.tasks[0]!;
40
28
  expect(task.inputs).toEqual(raw.tracks[0]!.tasks[0]!.inputs!);
41
29
  expect(task.outputs).toEqual(raw.tracks[0]!.tasks[0]!.outputs!);
42
30
  });
43
31
 
44
- test('raw ports survive onto the resolved task', () => {
45
- const raw: RawPipelineConfig = {
46
- name: 'p',
47
- tracks: [
48
- {
49
- id: 't',
50
- name: 'T',
51
- tasks: [
52
- {
53
- id: 'a',
54
- prompt: 'do it',
55
- ports: {
56
- inputs: [{ name: 'city', type: 'string', required: true }],
57
- outputs: [{ name: 'temp', type: 'number', description: 'Celsius' }],
58
- },
59
- },
60
- ],
61
- },
62
- ],
63
- };
64
- const resolved = resolveConfig(raw, WORK_DIR);
65
- const task = resolved.tracks[0]!.tasks[0]!;
66
- expect(task.ports).toBeDefined();
67
- expect(task.ports!.inputs).toEqual([
68
- { name: 'city', type: 'string', required: true },
69
- ]);
70
- expect(task.ports!.outputs).toEqual([
71
- { name: 'temp', type: 'number', description: 'Celsius' },
72
- ]);
73
- });
74
-
75
- test('tasks without ports still resolve with ports === undefined', () => {
76
- const raw: RawPipelineConfig = {
77
- name: 'p',
78
- tracks: [
79
- { id: 't', name: 'T', tasks: [{ id: 'a', prompt: 'do it' }] },
80
- ],
81
- };
82
- const resolved = resolveConfig(raw, WORK_DIR);
83
- expect(resolved.tracks[0]!.tasks[0]!.ports).toBeUndefined();
84
- });
85
-
86
- test('ports is not inherited from track or pipeline', () => {
87
- // Ports describe a per-task I/O contract. If we accidentally pulled
88
- // them from track defaults, two tasks in the same track would share
89
- // input ports and downstream data-flow would be ambiguous. Test that
90
- // a track with an unrelated `middlewares` default doesn't spread
91
- // anywhere unexpected — purely a regression guard for the no-inherit
92
- // invariant.
93
- const raw: RawPipelineConfig = {
94
- name: 'p',
95
- tracks: [
96
- {
97
- id: 't',
98
- name: 'T',
99
- middlewares: [{ type: 'static_context', file: './x' }],
100
- tasks: [{ id: 'a', prompt: 'x' }, { id: 'b', prompt: 'y' }],
101
- },
102
- ],
103
- };
104
- const resolved = resolveConfig(raw, WORK_DIR);
105
- for (const task of resolved.tracks[0]!.tasks) {
106
- expect(task.ports).toBeUndefined();
107
- }
108
- });
109
- });
110
-
111
- // ─── deresolvePipeline preserves ports ───────────────────────────────
112
-
113
- describe('deresolvePipeline — ports round-trip', () => {
114
- test('lightweight bindings round-trip', () => {
32
+ test('typed inputs and outputs round-trip through deresolve', () => {
115
33
  const raw: RawPipelineConfig = {
116
34
  name: 'p',
117
35
  tracks: [
@@ -123,103 +41,49 @@ describe('deresolvePipeline — ports round-trip', () => {
123
41
  id: 'a',
124
42
  command: 'echo "{{inputs.city}}"',
125
43
  inputs: {
126
- city: { from: 't.plan.outputs.city', required: true },
127
- mode: { default: 'quick' },
128
- },
129
- outputs: {
130
- raw: { from: 'stdout' },
44
+ city: {
45
+ from: 't.plan.outputs.city',
46
+ type: 'enum',
47
+ enum: ['Shanghai', 'Paris'],
48
+ required: true,
49
+ },
131
50
  },
51
+ outputs: { raw: { from: 'stdout' } },
132
52
  },
133
53
  ],
134
54
  },
135
55
  ],
136
56
  };
137
- const resolved = resolveConfig(raw, WORK_DIR);
138
- const back = deresolvePipeline(resolved, WORK_DIR);
57
+ const back = deresolvePipeline(resolveConfig(raw, WORK_DIR), WORK_DIR);
139
58
  expect(back.tracks[0]!.tasks[0]!.inputs).toEqual(raw.tracks[0]!.tasks[0]!.inputs!);
140
59
  expect(back.tracks[0]!.tasks[0]!.outputs).toEqual(raw.tracks[0]!.tasks[0]!.outputs!);
141
60
  });
142
61
 
143
- test('ports with both inputs and outputs round-trip', () => {
144
- const raw: RawPipelineConfig = {
145
- name: 'p',
146
- tracks: [
147
- {
148
- id: 't',
149
- name: 'T',
150
- tasks: [
151
- {
152
- id: 'a',
153
- prompt: 'hi',
154
- ports: {
155
- inputs: [{ name: 'city', type: 'string', required: true }],
156
- outputs: [{ name: 'temp', type: 'number' }],
157
- },
158
- },
159
- ],
160
- },
161
- ],
162
- };
163
- const resolved = resolveConfig(raw, WORK_DIR);
164
- const back = deresolvePipeline(resolved, WORK_DIR);
165
- expect(back.tracks[0]!.tasks[0]!.ports).toEqual(raw.tracks[0]!.tasks[0]!.ports!);
166
- });
167
-
168
- test('ports with only outputs round-trip', () => {
169
- const raw: RawPipelineConfig = {
170
- name: 'p',
171
- tracks: [
172
- {
173
- id: 't',
174
- name: 'T',
175
- tasks: [
176
- {
177
- id: 'a',
178
- command: 'echo hi',
179
- ports: { outputs: [{ name: 'x', type: 'string' }] },
180
- },
181
- ],
182
- },
183
- ],
184
- };
185
- const resolved = resolveConfig(raw, WORK_DIR);
186
- const back = deresolvePipeline(resolved, WORK_DIR);
187
- expect(back.tracks[0]!.tasks[0]!.ports).toEqual({
188
- outputs: [{ name: 'x', type: 'string' }],
189
- });
190
- });
191
-
192
- test('empty ports ({}) is dropped on deresolve', () => {
193
- // YAML round-trip prefers field absence over `ports: {}` so a task
194
- // that once declared a port but had it cleared in the editor
195
- // doesn't persist a useless empty object in the file.
62
+ test('empty binding maps are dropped on deresolve', () => {
196
63
  const resolved: PipelineConfig = {
197
64
  name: 'p',
198
65
  tracks: [
199
66
  {
200
67
  id: 't',
201
68
  name: 'T',
202
- driver: 'opencode',
203
- permissions: { read: true, write: false, execute: false },
204
- on_failure: 'skip_downstream',
205
69
  tasks: [
206
70
  {
207
71
  id: 'a',
208
72
  name: 'a',
209
73
  prompt: 'hi',
210
- permissions: { read: true, write: false, execute: false },
211
- driver: 'opencode',
212
- ports: {},
74
+ inputs: {},
75
+ outputs: {},
213
76
  },
214
77
  ],
215
78
  },
216
79
  ],
217
80
  };
218
81
  const back = deresolvePipeline(resolved, WORK_DIR);
219
- expect(back.tracks[0]!.tasks[0]!.ports).toBeUndefined();
82
+ expect(back.tracks[0]!.tasks[0]!.inputs).toBeUndefined();
83
+ expect(back.tracks[0]!.tasks[0]!.outputs).toBeUndefined();
220
84
  });
221
85
 
222
- test('YAML round-trip via serializePipeline preserves the full ports shape', () => {
86
+ test('YAML round-trip preserves typed unified binding shape', () => {
223
87
  const raw: RawPipelineConfig = {
224
88
  name: 'p',
225
89
  tracks: [
@@ -230,18 +94,13 @@ describe('deresolvePipeline — ports round-trip', () => {
230
94
  {
231
95
  id: 'classify',
232
96
  prompt: 'pick a bucket',
233
- ports: {
234
- inputs: [
235
- { name: 'doc', type: 'string', required: true, description: 'Full text' },
236
- ],
237
- outputs: [
238
- {
239
- name: 'bucket',
240
- type: 'enum',
241
- enum: ['spam', 'ham'],
242
- description: 'Classification',
243
- },
244
- ],
97
+ inputs: { doc: { type: 'string', required: true, description: 'Full text' } },
98
+ outputs: {
99
+ bucket: {
100
+ type: 'enum',
101
+ enum: ['spam', 'ham'],
102
+ description: 'Classification',
103
+ },
245
104
  },
246
105
  },
247
106
  ],
@@ -250,14 +109,11 @@ describe('deresolvePipeline — ports round-trip', () => {
250
109
  };
251
110
  const yamlText = serializePipeline(raw);
252
111
  const parsed = (yaml.load(yamlText) as { pipeline: RawPipelineConfig }).pipeline;
253
- expect(parsed.tracks[0]!.tasks[0]!.ports).toEqual(raw.tracks[0]!.tasks[0]!.ports!);
112
+ expect(parsed.tracks[0]!.tasks[0]!.inputs).toEqual(raw.tracks[0]!.tasks[0]!.inputs!);
113
+ expect(parsed.tracks[0]!.tasks[0]!.outputs).toEqual(raw.tracks[0]!.tasks[0]!.outputs!);
254
114
  });
255
- });
256
-
257
- // ─── parseYaml accepts ports ─────────────────────────────────────────
258
115
 
259
- describe('parseYaml accepts ports declarations', () => {
260
- test('real-world YAML with lightweight bindings parses cleanly', () => {
116
+ test('real-world YAML with typed bindings parses cleanly', () => {
261
117
  const text = `pipeline:
262
118
  name: demo
263
119
  tracks:
@@ -267,56 +123,27 @@ describe('parseYaml — accepts ports declarations', () => {
267
123
  - id: build
268
124
  command: bun run build
269
125
  outputs:
270
- bundlePath: { from: json.bundlePath }
126
+ bundlePath:
127
+ from: json.bundlePath
128
+ type: string
271
129
  - id: test
272
130
  depends_on: [build]
273
131
  command: 'bun test "{{inputs.bundlePath}}"'
274
132
  inputs:
275
133
  bundlePath:
276
134
  from: t.build.outputs.bundlePath
135
+ type: string
277
136
  required: true
278
137
  `;
279
138
  const config = parseYaml(text);
280
- const build = config.tracks[0]!.tasks[0]!;
281
- const testTask = config.tracks[0]!.tasks[1]!;
282
- expect(build.outputs!.bundlePath).toEqual({ from: 'json.bundlePath' });
283
- expect(testTask.inputs!.bundlePath).toEqual({
139
+ expect(config.tracks[0]!.tasks[0]!.outputs!.bundlePath).toEqual({
140
+ from: 'json.bundlePath',
141
+ type: 'string',
142
+ });
143
+ expect(config.tracks[0]!.tasks[1]!.inputs!.bundlePath).toEqual({
284
144
  from: 't.build.outputs.bundlePath',
145
+ type: 'string',
285
146
  required: true,
286
147
  });
287
148
  });
288
-
289
- test('real-world YAML with ports parses cleanly', () => {
290
- const text = `pipeline:
291
- name: demo
292
- tracks:
293
- - id: t
294
- name: Main
295
- tasks:
296
- - id: plan
297
- prompt: Pick a city and id
298
- ports:
299
- outputs:
300
- - name: city
301
- type: string
302
- description: Target city
303
- - name: id
304
- type: number
305
- - id: fetch
306
- depends_on: [plan]
307
- command: 'weather.sh --city "{{inputs.city}}" --id {{inputs.id}}'
308
- ports:
309
- inputs:
310
- - { name: city, type: string, required: true }
311
- - { name: id, type: number, required: true }
312
- outputs:
313
- - { name: temp, type: number }
314
- `;
315
- const config = parseYaml(text);
316
- const plan = config.tracks[0]!.tasks[0]!;
317
- const fetch = config.tracks[0]!.tasks[1]!;
318
- expect(plan.ports!.outputs!.map((p) => p.name)).toEqual(['city', 'id']);
319
- expect(fetch.ports!.inputs!.map((p) => p.name)).toEqual(['city', 'id']);
320
- expect(fetch.ports!.outputs!.map((p) => p.name)).toEqual(['temp']);
321
- });
322
149
  });
@@ -0,0 +1,84 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { createTagma } from './tagma';
6
+ import type { DriverPlugin, PipelineConfig } from './types';
7
+
8
+ function makeDir(prefix: string): string {
9
+ return mkdtempSync(join(tmpdir(), prefix));
10
+ }
11
+
12
+ function makeDriver(name: string, marker: string[]): DriverPlugin {
13
+ return {
14
+ name,
15
+ capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false },
16
+ async buildCommand() {
17
+ marker.push(name);
18
+ return { args: ['echo', name] };
19
+ },
20
+ };
21
+ }
22
+
23
+ describe('createTagma', () => {
24
+ test('instances own isolated plugin registries', () => {
25
+ const seenA: string[] = [];
26
+ const seenB: string[] = [];
27
+ const tagmaA = createTagma({ builtins: false });
28
+ const tagmaB = createTagma({ builtins: false });
29
+
30
+ tagmaA.registry.registerPlugin('drivers', 'mock', makeDriver('driver-a', seenA));
31
+ tagmaB.registry.registerPlugin('drivers', 'mock', makeDriver('driver-b', seenB));
32
+
33
+ expect(tagmaA.registry.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('driver-a');
34
+ expect(tagmaB.registry.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('driver-b');
35
+ expect(seenA).toEqual([]);
36
+ expect(seenB).toEqual([]);
37
+ });
38
+
39
+ test('run uses only the instance registry', async () => {
40
+ const tagma = createTagma({ builtins: false });
41
+ const dir = makeDir('tagma-instance-run-');
42
+ try {
43
+ await expect(
44
+ tagma.run(
45
+ {
46
+ name: 'instance-run',
47
+ tracks: [
48
+ {
49
+ id: 't',
50
+ name: 'T',
51
+ tasks: [{ id: 'prompt', name: 'prompt', prompt: 'hello' }],
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ cwd: dir,
57
+ skipPluginLoading: true,
58
+ },
59
+ ),
60
+ ).rejects.toThrow(/driver "opencode" not registered/);
61
+ } finally {
62
+ rmSync(dir, { recursive: true, force: true });
63
+ }
64
+ });
65
+
66
+ test('validate returns structural pipeline errors without running tasks', () => {
67
+ const tagma = createTagma({ builtins: false });
68
+
69
+ expect(
70
+ tagma.validate({
71
+ name: 'invalid',
72
+ tracks: [
73
+ {
74
+ id: 't',
75
+ name: 'T',
76
+ tasks: [
77
+ { id: 'a', name: 'A', command: 'echo a', depends_on: ['missing'] },
78
+ ],
79
+ },
80
+ ],
81
+ }),
82
+ ).toEqual(['Task reference "missing" not found']);
83
+ });
84
+ });
package/src/tagma.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { runPipeline, type EngineResult, type RunPipelineOptions } from './engine';
2
+ import { bootstrapBuiltins } from './bootstrap';
3
+ import { PluginRegistry } from './registry';
4
+ import { validateConfig } from './schema';
5
+ import type { PipelineConfig } from './types';
6
+
7
+ export interface CreateTagmaOptions {
8
+ /**
9
+ * Registry used by this SDK instance. Omit to create an isolated registry.
10
+ */
11
+ readonly registry?: PluginRegistry;
12
+ /**
13
+ * Register built-in drivers/triggers/completions/middlewares into the
14
+ * instance registry. Defaults to true.
15
+ */
16
+ readonly builtins?: boolean;
17
+ }
18
+
19
+ export interface TagmaRunOptions extends Omit<RunPipelineOptions, 'registry'> {
20
+ readonly cwd: string;
21
+ }
22
+
23
+ export interface Tagma {
24
+ readonly registry: PluginRegistry;
25
+ run(config: PipelineConfig, options: TagmaRunOptions): Promise<EngineResult>;
26
+ validate(config: PipelineConfig): readonly string[];
27
+ }
28
+
29
+ export function createTagma(options: CreateTagmaOptions = {}): Tagma {
30
+ const registry = options.registry ?? new PluginRegistry();
31
+ if (options.builtins !== false) {
32
+ bootstrapBuiltins(registry);
33
+ }
34
+
35
+ return {
36
+ registry,
37
+ run(config, { cwd, ...runOptions }) {
38
+ return runPipeline(config, cwd, {
39
+ ...runOptions,
40
+ registry,
41
+ });
42
+ },
43
+ validate(config) {
44
+ return validateConfig(config);
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ parseDuration,
3
+ validatePath,
4
+ generateRunId,
5
+ nowISO,
6
+ truncateForName,
7
+ } from './utils';
8
+