@sweny-ai/core 0.1.76 → 0.1.78

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 (123) hide show
  1. package/dist/browser.d.ts +11 -5
  2. package/dist/browser.js +11 -11
  3. package/dist/claude.d.ts +20 -0
  4. package/dist/claude.js +139 -9
  5. package/dist/cli/config.d.ts +22 -0
  6. package/dist/cli/config.js +56 -9
  7. package/dist/cli/main.d.ts +1 -1
  8. package/dist/cli/main.js +56 -57
  9. package/dist/cli/marketplace.js +35 -9
  10. package/dist/cli/publish.d.ts +10 -0
  11. package/dist/cli/publish.js +22 -14
  12. package/dist/executor.js +104 -43
  13. package/dist/loader.d.ts +40 -0
  14. package/dist/loader.js +73 -0
  15. package/dist/mcp-catalog.d.ts +47 -0
  16. package/dist/mcp-catalog.js +304 -0
  17. package/dist/mcp.d.ts +6 -9
  18. package/dist/mcp.js +24 -314
  19. package/dist/requires.d.ts +7 -0
  20. package/dist/requires.js +34 -0
  21. package/dist/retry.d.ts +23 -0
  22. package/dist/retry.js +72 -0
  23. package/dist/schema.d.ts +667 -80
  24. package/dist/schema.js +144 -5
  25. package/dist/skills/custom-loader.d.ts +40 -0
  26. package/dist/skills/custom-loader.js +107 -32
  27. package/dist/skills/index.d.ts +13 -1
  28. package/dist/skills/index.js +12 -3
  29. package/dist/studio.js +4 -1
  30. package/dist/templates.d.ts +29 -8
  31. package/dist/templates.js +30 -20
  32. package/dist/testing.d.ts +7 -0
  33. package/dist/testing.js +7 -0
  34. package/dist/types.d.ts +76 -0
  35. package/dist/verify.d.ts +1 -0
  36. package/dist/verify.js +43 -5
  37. package/package.json +8 -4
  38. package/dist/__tests__/cascade.test.d.ts +0 -11
  39. package/dist/__tests__/cascade.test.js +0 -449
  40. package/dist/__tests__/claude.test.d.ts +0 -1
  41. package/dist/__tests__/claude.test.js +0 -427
  42. package/dist/__tests__/custom-loader.test.d.ts +0 -1
  43. package/dist/__tests__/custom-loader.test.js +0 -116
  44. package/dist/__tests__/executor.test.d.ts +0 -1
  45. package/dist/__tests__/executor.test.js +0 -736
  46. package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
  47. package/dist/__tests__/integration/datadog.integration.test.js +0 -23
  48. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
  49. package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
  50. package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
  51. package/dist/__tests__/integration/github.integration.test.js +0 -37
  52. package/dist/__tests__/integration/harness.d.ts +0 -24
  53. package/dist/__tests__/integration/harness.js +0 -34
  54. package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
  55. package/dist/__tests__/integration/linear.integration.test.js +0 -15
  56. package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
  57. package/dist/__tests__/integration/sentry.integration.test.js +0 -20
  58. package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
  59. package/dist/__tests__/integration/slack.integration.test.js +0 -22
  60. package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
  61. package/dist/__tests__/integration/sources.integration.test.js +0 -123
  62. package/dist/__tests__/publish.test.d.ts +0 -1
  63. package/dist/__tests__/publish.test.js +0 -180
  64. package/dist/__tests__/schema.test.d.ts +0 -1
  65. package/dist/__tests__/schema.test.js +0 -526
  66. package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
  67. package/dist/__tests__/skill-config-integration.test.js +0 -204
  68. package/dist/__tests__/skills-index.test.d.ts +0 -1
  69. package/dist/__tests__/skills-index.test.js +0 -119
  70. package/dist/__tests__/skills.test.d.ts +0 -1
  71. package/dist/__tests__/skills.test.js +0 -565
  72. package/dist/__tests__/spec-conformance.test.d.ts +0 -18
  73. package/dist/__tests__/spec-conformance.test.js +0 -1074
  74. package/dist/__tests__/studio.test.d.ts +0 -1
  75. package/dist/__tests__/studio.test.js +0 -403
  76. package/dist/__tests__/templates.test.d.ts +0 -1
  77. package/dist/__tests__/templates.test.js +0 -49
  78. package/dist/__tests__/testing.test.d.ts +0 -1
  79. package/dist/__tests__/testing.test.js +0 -224
  80. package/dist/__tests__/verify.test.d.ts +0 -1
  81. package/dist/__tests__/verify.test.js +0 -517
  82. package/dist/__tests__/version-consistency.test.d.ts +0 -1
  83. package/dist/__tests__/version-consistency.test.js +0 -312
  84. package/dist/__tests__/workflow-builder.test.d.ts +0 -1
  85. package/dist/__tests__/workflow-builder.test.js +0 -110
  86. package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
  87. package/dist/__tests__/workflow-yaml.test.js +0 -201
  88. package/dist/cli/__tests__/diagram.test.d.ts +0 -1
  89. package/dist/cli/__tests__/diagram.test.js +0 -225
  90. package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
  91. package/dist/cli/__tests__/report-cloud.test.js +0 -165
  92. package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
  93. package/dist/cli/__tests__/upgrade.test.js +0 -317
  94. package/dist/cli/__tests__/version-check.test.d.ts +0 -1
  95. package/dist/cli/__tests__/version-check.test.js +0 -209
  96. package/dist/cli/config.test.d.ts +0 -1
  97. package/dist/cli/config.test.js +0 -200
  98. package/dist/cli/credentials.test.d.ts +0 -1
  99. package/dist/cli/credentials.test.js +0 -172
  100. package/dist/cli/e2e.test.d.ts +0 -1
  101. package/dist/cli/e2e.test.js +0 -690
  102. package/dist/cli/marketplace.test.d.ts +0 -1
  103. package/dist/cli/marketplace.test.js +0 -355
  104. package/dist/cli/new.test.d.ts +0 -1
  105. package/dist/cli/new.test.js +0 -1043
  106. package/dist/cli/output.test.d.ts +0 -1
  107. package/dist/cli/output.test.js +0 -180
  108. package/dist/cli/publish.test.d.ts +0 -1
  109. package/dist/cli/publish.test.js +0 -610
  110. package/dist/cli/renderer.test.d.ts +0 -1
  111. package/dist/cli/renderer.test.js +0 -302
  112. package/dist/executor.test.d.ts +0 -1
  113. package/dist/executor.test.js +0 -603
  114. package/dist/mcp.test.d.ts +0 -1
  115. package/dist/mcp.test.js +0 -735
  116. package/dist/mermaid.test.d.ts +0 -1
  117. package/dist/mermaid.test.js +0 -256
  118. package/dist/sources.test.d.ts +0 -1
  119. package/dist/sources.test.js +0 -265
  120. package/dist/sources.validation.test.d.ts +0 -12
  121. package/dist/sources.validation.test.js +0 -380
  122. package/dist/workflow-builder.test.d.ts +0 -1
  123. package/dist/workflow-builder.test.js +0 -117
@@ -1 +0,0 @@
1
- export {};
@@ -1,403 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { workflowToFlow, flowToWorkflow, applyExecutionEvent, exportAsTypescript, getSkillCatalog } from "../studio.js";
3
- const testWorkflow = {
4
- id: "test-studio",
5
- name: "Studio Test",
6
- description: "A→B, A→C",
7
- entry: "a",
8
- nodes: {
9
- a: { name: "Node A", instruction: "Do A", skills: ["github"] },
10
- b: { name: "Node B", instruction: "Do B", skills: [] },
11
- c: { name: "Node C", instruction: "Do C", skills: ["slack"] },
12
- },
13
- edges: [
14
- { from: "a", to: "b" },
15
- { from: "a", to: "c", when: "needs notification" },
16
- ],
17
- };
18
- describe("workflowToFlow", () => {
19
- it("converts nodes to FlowNodes", () => {
20
- const { nodes } = workflowToFlow(testWorkflow);
21
- expect(nodes).toHaveLength(3);
22
- const nodeA = nodes.find((n) => n.id === "a");
23
- expect(nodeA?.type).toBe("skillNode");
24
- expect(nodeA?.data.isEntry).toBe(true);
25
- expect(nodeA?.data.nodeId).toBe("a");
26
- expect(nodeA?.data.node.name).toBe("Node A");
27
- });
28
- it("marks terminal nodes", () => {
29
- const { nodes } = workflowToFlow(testWorkflow);
30
- const nodeB = nodes.find((n) => n.id === "b");
31
- const nodeC = nodes.find((n) => n.id === "c");
32
- expect(nodeB?.data.isTerminal).toBe(true);
33
- expect(nodeC?.data.isTerminal).toBe(true);
34
- // a is not terminal (it has outgoing edges)
35
- const nodeA = nodes.find((n) => n.id === "a");
36
- expect(nodeA?.data.isTerminal).toBe(false);
37
- });
38
- it("converts edges to FlowEdges", () => {
39
- const { edges } = workflowToFlow(testWorkflow);
40
- expect(edges).toHaveLength(2);
41
- const unconditional = edges.find((e) => e.source === "a" && e.target === "b");
42
- expect(unconditional?.data.isConditional).toBe(false);
43
- expect(unconditional?.data.when).toBeUndefined();
44
- const conditional = edges.find((e) => e.source === "a" && e.target === "c");
45
- expect(conditional?.data.isConditional).toBe(true);
46
- expect(conditional?.data.when).toBe("needs notification");
47
- });
48
- it("resolves skill metadata from catalog", () => {
49
- const { nodes } = workflowToFlow(testWorkflow);
50
- const nodeA = nodes.find((n) => n.id === "a");
51
- expect(nodeA?.data.skills).toHaveLength(1);
52
- expect(nodeA?.data.skills[0].id).toBe("github");
53
- expect(nodeA?.data.skills[0].name).toBe("GitHub");
54
- expect(nodeA?.data.skills[0].toolCount).toBeGreaterThan(0);
55
- });
56
- it("handles unknown skills gracefully", () => {
57
- const { nodes } = workflowToFlow(testWorkflow, []); // empty catalog
58
- const nodeA = nodes.find((n) => n.id === "a");
59
- expect(nodeA?.data.skills[0].name).toBe("github"); // falls back to ID
60
- expect(nodeA?.data.skills[0].toolCount).toBe(0);
61
- });
62
- it("initializes all nodes as pending", () => {
63
- const { nodes } = workflowToFlow(testWorkflow);
64
- for (const node of nodes) {
65
- expect(node.data.exec.status).toBe("pending");
66
- }
67
- });
68
- // Defensive behavior: streaming LLM YAML (and user-authored
69
- // workflows) routinely arrive with fields omitted. The viewer
70
- // must not crash on these — it should render an empty state for
71
- // the missing pieces.
72
- describe("defensive handling of partial workflows", () => {
73
- it("treats a node with undefined skills as having no skills", () => {
74
- const partial = {
75
- id: "partial",
76
- name: "Partial",
77
- description: "",
78
- entry: "a",
79
- nodes: {
80
- // Cast to unknown→Node: we're deliberately simulating
81
- // what a partial YAML parse produces at runtime.
82
- a: { name: "A", instruction: "Do A" },
83
- },
84
- edges: [],
85
- };
86
- expect(() => workflowToFlow(partial)).not.toThrow();
87
- const { nodes } = workflowToFlow(partial);
88
- expect(nodes[0].data.skills).toEqual([]);
89
- });
90
- it("treats a workflow with undefined edges as having no edges", () => {
91
- const partial = {
92
- id: "partial",
93
- name: "Partial",
94
- description: "",
95
- entry: "a",
96
- nodes: {
97
- a: { name: "A", instruction: "Do A", skills: [] },
98
- },
99
- };
100
- expect(() => workflowToFlow(partial)).not.toThrow();
101
- const { nodes, edges } = workflowToFlow(partial);
102
- expect(edges).toEqual([]);
103
- // The sole node is still terminal because there are no outgoing edges.
104
- expect(nodes[0].data.isTerminal).toBe(true);
105
- });
106
- it("treats a workflow with undefined nodes as having no nodes", () => {
107
- const partial = {
108
- id: "partial",
109
- name: "Partial",
110
- description: "",
111
- entry: "a",
112
- edges: [],
113
- };
114
- expect(() => workflowToFlow(partial)).not.toThrow();
115
- const { nodes, edges } = workflowToFlow(partial);
116
- expect(nodes).toEqual([]);
117
- expect(edges).toEqual([]);
118
- });
119
- it("treats a skill catalog entry with missing tools as toolCount=0", () => {
120
- const brokenSkill = {
121
- id: "broken",
122
- name: "Broken",
123
- description: "No tools array",
124
- category: "general",
125
- config: {},
126
- // tools omitted on purpose
127
- };
128
- const wf = {
129
- id: "t",
130
- name: "T",
131
- description: "",
132
- entry: "a",
133
- nodes: { a: { name: "A", instruction: "Do A", skills: ["broken"] } },
134
- edges: [],
135
- };
136
- expect(() => workflowToFlow(wf, [brokenSkill])).not.toThrow();
137
- const { nodes } = workflowToFlow(wf, [brokenSkill]);
138
- expect(nodes[0].data.skills[0].toolCount).toBe(0);
139
- });
140
- });
141
- });
142
- describe("edge IDs and edgeIndex", () => {
143
- it("generates unique edge IDs using array index", () => {
144
- const { edges } = workflowToFlow(testWorkflow);
145
- const ids = edges.map((e) => e.id);
146
- expect(ids[0]).toBe("edge-0");
147
- expect(ids[1]).toBe("edge-1");
148
- // All IDs are unique
149
- expect(new Set(ids).size).toBe(ids.length);
150
- });
151
- it("assigns correct edgeIndex matching array position", () => {
152
- const { edges } = workflowToFlow(testWorkflow);
153
- edges.forEach((edge, i) => {
154
- expect(edge.edgeIndex).toBe(i);
155
- });
156
- });
157
- it("produces unique IDs for multiple edges between the same node pair", () => {
158
- const wf = {
159
- id: "multi-edge",
160
- name: "Multi-Edge",
161
- description: "",
162
- entry: "a",
163
- nodes: {
164
- a: { name: "A", instruction: "A", skills: [] },
165
- b: { name: "B", instruction: "B", skills: [] },
166
- },
167
- edges: [
168
- { from: "a", to: "b" },
169
- { from: "a", to: "b", when: "error detected" },
170
- { from: "a", to: "b", when: "retry needed" },
171
- ],
172
- };
173
- const { edges } = workflowToFlow(wf);
174
- expect(edges).toHaveLength(3);
175
- const ids = edges.map((e) => e.id);
176
- expect(new Set(ids).size).toBe(3);
177
- expect(ids).toEqual(["edge-0", "edge-1", "edge-2"]);
178
- });
179
- it("produces stable IDs across multiple conversions", () => {
180
- const first = workflowToFlow(testWorkflow).edges.map((e) => e.id);
181
- const second = workflowToFlow(testWorkflow).edges.map((e) => e.id);
182
- expect(first).toEqual(second);
183
- });
184
- });
185
- describe("max_iterations in conversion", () => {
186
- const wfWithMaxIter = {
187
- id: "retry-loop",
188
- name: "Retry Loop",
189
- description: "",
190
- entry: "check",
191
- nodes: {
192
- check: { name: "Check", instruction: "Check it", skills: [] },
193
- fix: { name: "Fix", instruction: "Fix it", skills: [] },
194
- },
195
- edges: [
196
- { from: "check", to: "fix", when: "issue found" },
197
- { from: "fix", to: "check", when: "needs re-check", max_iterations: 3 },
198
- ],
199
- };
200
- it("includes max_iterations in flow edge data", () => {
201
- const { edges } = workflowToFlow(wfWithMaxIter);
202
- const retryEdge = edges.find((e) => e.source === "fix" && e.target === "check");
203
- expect(retryEdge?.data.max_iterations).toBe(3);
204
- });
205
- it("omits max_iterations when not set", () => {
206
- const { edges } = workflowToFlow(wfWithMaxIter);
207
- const firstEdge = edges.find((e) => e.source === "check" && e.target === "fix");
208
- expect(firstEdge?.data.max_iterations).toBeUndefined();
209
- });
210
- it("round-trips max_iterations through flowToWorkflow", () => {
211
- const { nodes, edges } = workflowToFlow(wfWithMaxIter);
212
- const rebuilt = flowToWorkflow({
213
- id: wfWithMaxIter.id,
214
- name: wfWithMaxIter.name,
215
- description: wfWithMaxIter.description,
216
- entry: wfWithMaxIter.entry,
217
- }, nodes, edges);
218
- const retryEdge = rebuilt.edges.find((e) => e.from === "fix" && e.to === "check");
219
- expect(retryEdge?.max_iterations).toBe(3);
220
- // Non-retry edge should not have max_iterations
221
- const normalEdge = rebuilt.edges.find((e) => e.from === "check" && e.to === "fix");
222
- expect(normalEdge).not.toHaveProperty("max_iterations");
223
- });
224
- });
225
- describe("Source type preservation", () => {
226
- it("preserves inline string instructions through round-trip", () => {
227
- const { nodes, edges } = workflowToFlow(testWorkflow);
228
- const rebuilt = flowToWorkflow({ id: "x", name: "X", description: "", entry: "a" }, nodes, edges);
229
- expect(rebuilt.nodes.a.instruction).toBe("Do A");
230
- });
231
- it("preserves {file:} Source through round-trip", () => {
232
- const wf = {
233
- id: "file-src",
234
- name: "File Source",
235
- description: "",
236
- entry: "a",
237
- nodes: {
238
- a: { name: "A", instruction: { file: "./instructions/step-a.md" }, skills: [] },
239
- },
240
- edges: [],
241
- };
242
- const { nodes, edges } = workflowToFlow(wf);
243
- const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
244
- expect(rebuilt.nodes.a.instruction).toEqual({ file: "./instructions/step-a.md" });
245
- });
246
- it("preserves {url:} Source through round-trip", () => {
247
- const wf = {
248
- id: "url-src",
249
- name: "URL Source",
250
- description: "",
251
- entry: "a",
252
- nodes: {
253
- a: { name: "A", instruction: { url: "https://example.com/instruction.md" }, skills: [] },
254
- },
255
- edges: [],
256
- };
257
- const { nodes, edges } = workflowToFlow(wf);
258
- const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
259
- expect(rebuilt.nodes.a.instruction).toEqual({ url: "https://example.com/instruction.md" });
260
- });
261
- it("preserves {inline:} Source through round-trip", () => {
262
- const wf = {
263
- id: "inline-src",
264
- name: "Inline Source",
265
- description: "",
266
- entry: "a",
267
- nodes: {
268
- a: { name: "A", instruction: { inline: "Explicit inline text" }, skills: [] },
269
- },
270
- edges: [],
271
- };
272
- const { nodes, edges } = workflowToFlow(wf);
273
- const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
274
- expect(rebuilt.nodes.a.instruction).toEqual({ inline: "Explicit inline text" });
275
- });
276
- it("preserves {url:, type:} Source through round-trip", () => {
277
- const wf = {
278
- id: "url-type-src",
279
- name: "URL Type Source",
280
- description: "",
281
- entry: "a",
282
- nodes: {
283
- a: { name: "A", instruction: { url: "https://example.com/data", type: "fetch" }, skills: [] },
284
- },
285
- edges: [],
286
- };
287
- const { nodes, edges } = workflowToFlow(wf);
288
- const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
289
- const src = rebuilt.nodes.a.instruction;
290
- expect(src).toEqual({ url: "https://example.com/data", type: "fetch" });
291
- });
292
- });
293
- describe("flowToWorkflow", () => {
294
- it("round-trips correctly", () => {
295
- const { nodes, edges } = workflowToFlow(testWorkflow);
296
- const rebuilt = flowToWorkflow({
297
- id: testWorkflow.id,
298
- name: testWorkflow.name,
299
- description: testWorkflow.description,
300
- entry: testWorkflow.entry,
301
- }, nodes, edges);
302
- expect(rebuilt.id).toBe(testWorkflow.id);
303
- expect(rebuilt.entry).toBe(testWorkflow.entry);
304
- expect(Object.keys(rebuilt.nodes)).toHaveLength(3);
305
- expect(rebuilt.edges).toHaveLength(2);
306
- expect(rebuilt.edges.find((e) => e.from === "a" && e.to === "c")?.when).toBe("needs notification");
307
- });
308
- it("omits when field for unconditional edges", () => {
309
- const { nodes, edges } = workflowToFlow(testWorkflow);
310
- const rebuilt = flowToWorkflow({ id: "x", name: "X", description: "", entry: "a" }, nodes, edges);
311
- const unconditional = rebuilt.edges.find((e) => e.from === "a" && e.to === "b");
312
- expect(unconditional).not.toHaveProperty("when");
313
- });
314
- it("omits max_iterations when not set", () => {
315
- const { nodes, edges } = workflowToFlow(testWorkflow);
316
- const rebuilt = flowToWorkflow({ id: "x", name: "X", description: "", entry: "a" }, nodes, edges);
317
- for (const edge of rebuilt.edges) {
318
- expect(edge).not.toHaveProperty("max_iterations");
319
- }
320
- });
321
- });
322
- describe("applyExecutionEvent", () => {
323
- function makeNodeDataMap() {
324
- const { nodes } = workflowToFlow(testWorkflow);
325
- return new Map(nodes.map((n) => [n.id, n.data]));
326
- }
327
- it("resets all nodes on workflow:start", () => {
328
- const map = makeNodeDataMap();
329
- map.get("a").exec = { status: "success" };
330
- applyExecutionEvent({ type: "workflow:start", workflow: "test" }, map);
331
- expect(map.get("a").exec.status).toBe("pending");
332
- });
333
- it("marks node as running on node:enter", () => {
334
- const map = makeNodeDataMap();
335
- applyExecutionEvent({ type: "node:enter", node: "a", instruction: "Do A" }, map);
336
- expect(map.get("a").exec.status).toBe("running");
337
- });
338
- it("tracks current tool on tool:call", () => {
339
- const map = makeNodeDataMap();
340
- map.get("a").exec = { status: "running" };
341
- applyExecutionEvent({ type: "tool:call", node: "a", tool: "github_search_code", input: {} }, map);
342
- expect(map.get("a").exec.currentTool).toBe("github_search_code");
343
- });
344
- it("clears current tool on tool:result", () => {
345
- const map = makeNodeDataMap();
346
- map.get("a").exec = { status: "running", currentTool: "github_search_code" };
347
- applyExecutionEvent({ type: "tool:result", node: "a", tool: "github_search_code", output: {} }, map);
348
- expect(map.get("a").exec.currentTool).toBeUndefined();
349
- });
350
- it("marks node as success on node:exit with success", () => {
351
- const map = makeNodeDataMap();
352
- const result = { status: "success", data: {}, toolCalls: [] };
353
- applyExecutionEvent({ type: "node:exit", node: "a", result }, map);
354
- expect(map.get("a").exec.status).toBe("success");
355
- expect(map.get("a").exec.result).toBe(result);
356
- });
357
- it("marks node as failed on node:exit with failure", () => {
358
- const map = makeNodeDataMap();
359
- const result = { status: "failed", data: { error: "oops" }, toolCalls: [] };
360
- applyExecutionEvent({ type: "node:exit", node: "a", result }, map);
361
- expect(map.get("a").exec.status).toBe("failed");
362
- });
363
- it("ignores events for unknown nodes", () => {
364
- const map = makeNodeDataMap();
365
- // Should not throw
366
- applyExecutionEvent({ type: "node:enter", node: "ghost", instruction: "" }, map);
367
- expect(map.has("ghost")).toBe(false);
368
- });
369
- });
370
- describe("exportAsTypescript", () => {
371
- it("generates valid TypeScript", () => {
372
- const ts = exportAsTypescript(testWorkflow);
373
- expect(ts).toContain("import type { Workflow }");
374
- expect(ts).toContain("test_studio");
375
- expect(ts).toContain('"Studio Test"');
376
- });
377
- it("sanitizes id for variable name", () => {
378
- const wf = { ...testWorkflow, id: "my-special_workflow.v2" };
379
- const ts = exportAsTypescript(wf);
380
- expect(ts).toContain("my_special_workflow_v2");
381
- });
382
- });
383
- describe("getSkillCatalog", () => {
384
- it("returns builtin skills", () => {
385
- const catalog = getSkillCatalog();
386
- expect(catalog.length).toBeGreaterThan(0);
387
- const github = catalog.find((s) => s.id === "github");
388
- expect(github).toBeDefined();
389
- expect(github.tools.length).toBeGreaterThan(0);
390
- });
391
- it("includes extra skills", () => {
392
- const extra = {
393
- id: "custom",
394
- name: "Custom",
395
- description: "A custom skill",
396
- category: "general",
397
- config: {},
398
- tools: [{ name: "custom_tool", description: "Does custom", input_schema: {}, handler: async () => { } }],
399
- };
400
- const catalog = getSkillCatalog([extra]);
401
- expect(catalog.find((s) => s.id === "custom")).toBeDefined();
402
- });
403
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parse } from "yaml";
3
- import { WORKFLOW_TEMPLATES } from "../cli/templates.js";
4
- import { parseWorkflow, validateWorkflow } from "../schema.js";
5
- describe("WORKFLOW_TEMPLATES", () => {
6
- it("has at least 4 templates", () => {
7
- expect(WORKFLOW_TEMPLATES.length).toBeGreaterThanOrEqual(4);
8
- });
9
- it("each template has unique id, name, and description", () => {
10
- const ids = new Set();
11
- for (const t of WORKFLOW_TEMPLATES) {
12
- expect(t.id).toBeTruthy();
13
- expect(t.name).toBeTruthy();
14
- expect(t.description).toBeTruthy();
15
- expect(ids.has(t.id)).toBe(false);
16
- ids.add(t.id);
17
- }
18
- });
19
- for (const template of WORKFLOW_TEMPLATES) {
20
- describe(`template: ${template.id}`, () => {
21
- it("parses as valid YAML", () => {
22
- const parsed = parse(template.yaml);
23
- expect(parsed).toBeDefined();
24
- expect(parsed.id).toBe(template.id);
25
- });
26
- it("passes parseWorkflow validation", () => {
27
- const parsed = parse(template.yaml);
28
- const workflow = parseWorkflow(parsed);
29
- expect(workflow.id).toBe(template.id);
30
- expect(Object.keys(workflow.nodes).length).toBeGreaterThan(0);
31
- });
32
- it("passes validateWorkflow with no errors", () => {
33
- const parsed = parse(template.yaml);
34
- const workflow = parseWorkflow(parsed);
35
- const errors = validateWorkflow(workflow);
36
- expect(errors).toEqual([]);
37
- });
38
- it("uses only the github skill", () => {
39
- const parsed = parse(template.yaml);
40
- const workflow = parseWorkflow(parsed);
41
- for (const node of Object.values(workflow.nodes)) {
42
- for (const skill of node.skills) {
43
- expect(skill).toBe("github");
44
- }
45
- }
46
- });
47
- });
48
- }
49
- });
@@ -1 +0,0 @@
1
- export {};