@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,603 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
3
- import { rmSync } from "node:fs";
4
- import { randomBytes } from "node:crypto";
5
- import * as path from "node:path";
6
- import { tmpdir } from "node:os";
7
- import { execute } from "./executor.js";
8
- import { MockClaude, createFileSkill } from "./testing.js";
9
- import { createSkillMap } from "./skills/index.js";
10
- import { validateWorkflow, parseWorkflow } from "./schema.js";
11
- import { triageWorkflow, implementWorkflow } from "./workflows/index.js";
12
- // ─── Test fixtures ───────────────────────────────────────────────
13
- const tmpBase = path.join(tmpdir(), "sweny-core-test");
14
- let dirCounter = 0;
15
- function freshDir(name) {
16
- const dir = path.join(tmpBase, `${name}-${Date.now()}-${++dirCounter}-${randomBytes(4).toString("hex")}`);
17
- rmSync(dir, { recursive: true, force: true });
18
- mkdirSync(dir, { recursive: true });
19
- return dir;
20
- }
21
- // Minimal 3-node DAG for unit testing
22
- const simpleWorkflow = {
23
- id: "test-simple",
24
- name: "Simple Test",
25
- description: "A→B→C linear workflow",
26
- entry: "step-a",
27
- nodes: {
28
- "step-a": {
29
- name: "Step A",
30
- instruction: "Read the input and produce context",
31
- skills: ["filesystem"],
32
- },
33
- "step-b": {
34
- name: "Step B",
35
- instruction: "Process the context from step A",
36
- skills: ["filesystem"],
37
- },
38
- "step-c": {
39
- name: "Step C",
40
- instruction: "Write the final result",
41
- skills: ["filesystem"],
42
- },
43
- },
44
- edges: [
45
- { from: "step-a", to: "step-b" },
46
- { from: "step-b", to: "step-c" },
47
- ],
48
- };
49
- // Branching DAG for conditional routing
50
- const branchingWorkflow = {
51
- id: "test-branching",
52
- name: "Branching Test",
53
- description: "A → B or C depending on condition",
54
- entry: "check",
55
- nodes: {
56
- check: {
57
- name: "Check Input",
58
- instruction: "Examine the input and determine severity",
59
- skills: ["filesystem"],
60
- },
61
- handle_high: {
62
- name: "Handle High Severity",
63
- instruction: "Handle a high-severity alert",
64
- skills: ["filesystem"],
65
- },
66
- handle_low: {
67
- name: "Handle Low Severity",
68
- instruction: "Handle a low-severity alert",
69
- skills: [],
70
- },
71
- },
72
- edges: [
73
- { from: "check", to: "handle_high", when: "severity is high or critical" },
74
- { from: "check", to: "handle_low", when: "severity is low or medium" },
75
- ],
76
- };
77
- // ─── Schema validation tests ─────────────────────────────────────
78
- describe("schema validation", () => {
79
- it("validates a correct workflow", () => {
80
- const errors = validateWorkflow(simpleWorkflow);
81
- expect(errors).toEqual([]);
82
- });
83
- it("detects missing entry node", () => {
84
- const bad = { ...simpleWorkflow, entry: "nonexistent" };
85
- const errors = validateWorkflow(bad);
86
- expect(errors).toContainEqual(expect.objectContaining({ code: "MISSING_ENTRY" }));
87
- });
88
- it("detects unknown edge targets", () => {
89
- const bad = {
90
- ...simpleWorkflow,
91
- edges: [{ from: "step-a", to: "nonexistent" }],
92
- };
93
- const errors = validateWorkflow(bad);
94
- expect(errors).toContainEqual(expect.objectContaining({ code: "UNKNOWN_EDGE_TARGET" }));
95
- });
96
- it("detects unreachable nodes", () => {
97
- const disconnected = {
98
- ...simpleWorkflow,
99
- edges: [{ from: "step-a", to: "step-b" }],
100
- // step-c has no incoming edge
101
- };
102
- const errors = validateWorkflow(disconnected);
103
- expect(errors).toContainEqual(expect.objectContaining({ code: "UNREACHABLE_NODE", nodeId: "step-c" }));
104
- });
105
- it("detects self-loops", () => {
106
- const loopy = {
107
- ...simpleWorkflow,
108
- edges: [
109
- { from: "step-a", to: "step-a" },
110
- { from: "step-a", to: "step-b" },
111
- { from: "step-b", to: "step-c" },
112
- ],
113
- };
114
- const errors = validateWorkflow(loopy);
115
- expect(errors).toContainEqual(expect.objectContaining({ code: "SELF_LOOP", nodeId: "step-a" }));
116
- });
117
- it("detects unknown skill references", () => {
118
- const errors = validateWorkflow(simpleWorkflow, new Set(["github"]));
119
- expect(errors).toContainEqual(expect.objectContaining({ code: "UNKNOWN_SKILL", nodeId: "step-a" }));
120
- });
121
- it("validates the triage workflow", () => {
122
- const errors = validateWorkflow(triageWorkflow);
123
- expect(errors).toEqual([]);
124
- });
125
- it("validates the implement workflow", () => {
126
- const errors = validateWorkflow(implementWorkflow);
127
- expect(errors).toEqual([]);
128
- });
129
- it("parses a raw JSON workflow via Zod", () => {
130
- const raw = JSON.parse(JSON.stringify(simpleWorkflow));
131
- const parsed = parseWorkflow(raw);
132
- expect(parsed.id).toBe("test-simple");
133
- expect(parsed.nodes["step-a"].instruction).toBe("Read the input and produce context");
134
- });
135
- it("rejects invalid workflow JSON", () => {
136
- expect(() => parseWorkflow({ id: "" })).toThrow();
137
- expect(() => parseWorkflow({ id: "x", name: "x", nodes: {}, edges: [] })).toThrow(); // missing entry
138
- });
139
- });
140
- // ─── Executor tests ──────────────────────────────────────────────
141
- describe("executor", () => {
142
- let outputDir;
143
- beforeEach(() => {
144
- outputDir = freshDir("executor");
145
- });
146
- it("executes a linear workflow end-to-end", async () => {
147
- const fileSkill = createFileSkill(outputDir);
148
- const skills = createSkillMap([fileSkill]);
149
- // Write a fixture input file
150
- writeFileSync(path.join(outputDir, "input.json"), JSON.stringify({ alert: "test" }));
151
- const claude = new MockClaude({
152
- responses: {
153
- "step-a": {
154
- toolCalls: [{ tool: "fs_read_json", input: { path: "input.json" } }],
155
- data: { context: "loaded from file" },
156
- },
157
- "step-b": {
158
- data: { analysis: "processed" },
159
- },
160
- "step-c": {
161
- toolCalls: [
162
- {
163
- tool: "fs_write_json",
164
- input: { path: "result.json", data: { status: "done" } },
165
- },
166
- ],
167
- data: { result: "written" },
168
- },
169
- },
170
- });
171
- const events = [];
172
- const { results } = await execute(simpleWorkflow, { alert: "test" }, {
173
- skills,
174
- claude,
175
- observer: (e) => events.push(e),
176
- config: {},
177
- });
178
- // All 3 nodes executed
179
- expect(results.size).toBe(3);
180
- expect(results.get("step-a")?.status).toBe("success");
181
- expect(results.get("step-b")?.status).toBe("success");
182
- expect(results.get("step-c")?.status).toBe("success");
183
- // Node A read the input file
184
- expect(results.get("step-a")?.data.context).toBe("loaded from file");
185
- // Node C wrote the result file
186
- expect(existsSync(path.join(outputDir, "result.json"))).toBe(true);
187
- const written = JSON.parse(readFileSync(path.join(outputDir, "result.json"), "utf-8"));
188
- expect(written.status).toBe("done");
189
- // Events were emitted correctly — workflow:start fires first, then sources:resolved (per spec)
190
- expect(events[0]).toEqual({ type: "workflow:start", workflow: "test-simple" });
191
- expect(events[1].type).toBe("sources:resolved");
192
- const nodeEnters = events.filter((e) => e.type === "node:enter");
193
- expect(nodeEnters).toHaveLength(3);
194
- expect(events[events.length - 1].type).toBe("workflow:end");
195
- });
196
- it("executes conditional branches", async () => {
197
- const fileSkill = createFileSkill(outputDir);
198
- const skills = createSkillMap([fileSkill]);
199
- const claude = new MockClaude({
200
- responses: {
201
- check: { data: { severity: "high" } },
202
- handle_high: { data: { handled: true } },
203
- },
204
- routes: {
205
- check: "handle_high", // Route to high severity handler
206
- },
207
- });
208
- const { results } = await execute(branchingWorkflow, { alert: "cpu spike" }, {
209
- skills,
210
- claude,
211
- config: {},
212
- });
213
- expect(results.size).toBe(2);
214
- expect(results.has("check")).toBe(true);
215
- expect(results.has("handle_high")).toBe(true);
216
- expect(results.has("handle_low")).toBe(false);
217
- });
218
- it("routes to alternative branch", async () => {
219
- const fileSkill = createFileSkill(outputDir);
220
- const skills = createSkillMap([fileSkill]);
221
- const claude = new MockClaude({
222
- responses: {
223
- check: { data: { severity: "low" } },
224
- handle_low: { data: { skipped: true } },
225
- },
226
- routes: {
227
- check: "handle_low",
228
- },
229
- });
230
- const { results } = await execute(branchingWorkflow, { alert: "minor" }, {
231
- skills,
232
- claude,
233
- config: {},
234
- });
235
- expect(results.size).toBe(2);
236
- expect(results.has("handle_low")).toBe(true);
237
- expect(results.has("handle_high")).toBe(false);
238
- });
239
- it("emits tool:call and tool:result events", async () => {
240
- const fileSkill = createFileSkill(outputDir);
241
- writeFileSync(path.join(outputDir, "data.json"), '{"key": "value"}');
242
- const claude = new MockClaude({
243
- responses: {
244
- "step-a": {
245
- toolCalls: [{ tool: "fs_read_json", input: { path: "data.json" } }],
246
- data: { read: true },
247
- },
248
- "step-b": { data: {} },
249
- "step-c": { data: {} },
250
- },
251
- });
252
- const events = [];
253
- await execute(simpleWorkflow, {}, {
254
- skills: createSkillMap([fileSkill]),
255
- claude,
256
- observer: (e) => events.push(e),
257
- config: {},
258
- });
259
- const toolCalls = events.filter((e) => e.type === "tool:call");
260
- const toolResults = events.filter((e) => e.type === "tool:result");
261
- expect(toolCalls).toHaveLength(1);
262
- expect(toolResults).toHaveLength(1);
263
- expect(toolCalls[0]).toMatchObject({
264
- type: "tool:call",
265
- node: "step-a",
266
- tool: "fs_read_json",
267
- });
268
- });
269
- it("throws on missing entry node", async () => {
270
- const bad = { ...simpleWorkflow, entry: "nonexistent" };
271
- const claude = new MockClaude({ responses: {} });
272
- await expect(execute(bad, {}, { skills: createSkillMap([]), claude, config: {} })).rejects.toThrow("Entry node");
273
- });
274
- it("throws on invalid edge reference", async () => {
275
- const bad = {
276
- ...simpleWorkflow,
277
- edges: [
278
- { from: "step-a", to: "nonexistent" },
279
- { from: "step-a", to: "step-b" },
280
- ],
281
- };
282
- const claude = new MockClaude({ responses: {} });
283
- await expect(execute(bad, {}, { skills: createSkillMap([]), claude, config: {} })).rejects.toThrow("unknown node");
284
- });
285
- });
286
- // ─── File skill tests ────────────────────────────────────────────
287
- describe("file skill", () => {
288
- let outputDir;
289
- beforeEach(() => {
290
- outputDir = freshDir("fileskill");
291
- });
292
- it("reads and writes JSON", async () => {
293
- const skill = createFileSkill(outputDir);
294
- const write = skill.tools.find((t) => t.name === "fs_write_json");
295
- const read = skill.tools.find((t) => t.name === "fs_read_json");
296
- const ctx = { config: {}, logger: console };
297
- await write.handler({ path: "test.json", data: { hello: "world" } }, ctx);
298
- const result = await read.handler({ path: "test.json" }, ctx);
299
- expect(result).toEqual({ hello: "world" });
300
- });
301
- it("writes markdown", async () => {
302
- const skill = createFileSkill(outputDir);
303
- const write = skill.tools.find((t) => t.name === "fs_write_markdown");
304
- const ctx = { config: {}, logger: console };
305
- await write.handler({ path: "issues/ISSUE-1.md", content: "# Bug\n\nSomething broke" }, ctx);
306
- const content = readFileSync(path.join(outputDir, "issues/ISSUE-1.md"), "utf-8");
307
- expect(content).toContain("# Bug");
308
- });
309
- it("creates nested directories", async () => {
310
- const skill = createFileSkill(outputDir);
311
- const write = skill.tools.find((t) => t.name === "fs_write_json");
312
- const ctx = { config: {}, logger: console };
313
- await write.handler({ path: "deep/nested/file.json", data: { ok: true } }, ctx);
314
- expect(existsSync(path.join(outputDir, "deep/nested/file.json"))).toBe(true);
315
- });
316
- });
317
- // ─── Skill instruction injection tests ──────────────────────────
318
- describe("skill instruction injection", () => {
319
- let outputDir;
320
- beforeEach(() => {
321
- outputDir = freshDir("skill-inject");
322
- });
323
- it("injects skill instructions into the prompt", async () => {
324
- const instructionSkill = {
325
- id: "code-standards",
326
- name: "Code Standards",
327
- description: "Team conventions",
328
- category: "general",
329
- config: {},
330
- tools: [],
331
- instruction: "Always use camelCase for variable names.",
332
- };
333
- const workflow = {
334
- id: "test-inject",
335
- name: "Inject Test",
336
- description: "",
337
- entry: "step",
338
- nodes: {
339
- step: {
340
- name: "Do Work",
341
- instruction: "Write some code.",
342
- skills: ["code-standards"],
343
- },
344
- },
345
- edges: [],
346
- };
347
- let capturedInstruction = "";
348
- const claude = {
349
- async run(opts) {
350
- capturedInstruction = opts.instruction;
351
- return { status: "success", data: {}, toolCalls: [] };
352
- },
353
- async evaluate() {
354
- return "";
355
- },
356
- };
357
- await execute(workflow, {}, {
358
- skills: createSkillMap([instructionSkill]),
359
- claude,
360
- config: {},
361
- });
362
- expect(capturedInstruction).toContain("## Skill: Code Standards");
363
- expect(capturedInstruction).toContain("Always use camelCase for variable names.");
364
- expect(capturedInstruction).toContain("Write some code.");
365
- });
366
- it("injects multiple skill instructions in array order", async () => {
367
- const skillA = {
368
- id: "skill-a",
369
- name: "Skill A",
370
- description: "First",
371
- category: "general",
372
- config: {},
373
- tools: [],
374
- instruction: "AAA instruction",
375
- };
376
- const skillB = {
377
- id: "skill-b",
378
- name: "Skill B",
379
- description: "Second",
380
- category: "general",
381
- config: {},
382
- tools: [],
383
- instruction: "BBB instruction",
384
- };
385
- const workflow = {
386
- id: "test-multi",
387
- name: "Multi Inject",
388
- description: "",
389
- entry: "step",
390
- nodes: {
391
- step: {
392
- name: "Work",
393
- instruction: "Do the work.",
394
- skills: ["skill-a", "skill-b"],
395
- },
396
- },
397
- edges: [],
398
- };
399
- let capturedInstruction = "";
400
- const claude = {
401
- async run(opts) {
402
- capturedInstruction = opts.instruction;
403
- return { status: "success", data: {}, toolCalls: [] };
404
- },
405
- async evaluate() {
406
- return "";
407
- },
408
- };
409
- await execute(workflow, {}, {
410
- skills: createSkillMap([skillA, skillB]),
411
- claude,
412
- config: {},
413
- });
414
- const posA = capturedInstruction.indexOf("AAA instruction");
415
- const posB = capturedInstruction.indexOf("BBB instruction");
416
- const posBase = capturedInstruction.indexOf("Do the work.");
417
- expect(posA).toBeLessThan(posB);
418
- expect(posB).toBeLessThan(posBase);
419
- });
420
- it("allows instruction-only nodes (no tools, no error)", async () => {
421
- const instructionSkill = {
422
- id: "rubric",
423
- name: "Evaluation Rubric",
424
- description: "Scoring criteria",
425
- category: "general",
426
- config: {},
427
- tools: [],
428
- instruction: "Score quality 1-5.",
429
- };
430
- const workflow = {
431
- id: "test-notools",
432
- name: "No Tools",
433
- description: "",
434
- entry: "judge",
435
- nodes: {
436
- judge: {
437
- name: "Judge",
438
- instruction: "Evaluate the output.",
439
- skills: ["rubric"],
440
- },
441
- },
442
- edges: [],
443
- };
444
- const claude = {
445
- async run() {
446
- return { status: "success", data: { score: 4 }, toolCalls: [] };
447
- },
448
- async evaluate() {
449
- return "";
450
- },
451
- };
452
- // Should NOT throw "none are configured"
453
- const { results } = await execute(workflow, {}, {
454
- skills: createSkillMap([instructionSkill]),
455
- claude,
456
- config: {},
457
- });
458
- expect(results.get("judge")?.status).toBe("success");
459
- });
460
- it("wires inline workflow.skills into the executor automatically", async () => {
461
- const workflow = {
462
- id: "test-inline",
463
- name: "Inline Skills",
464
- description: "",
465
- entry: "review",
466
- nodes: {
467
- review: {
468
- name: "Review",
469
- instruction: "Review the code.",
470
- skills: ["my-rubric"],
471
- },
472
- },
473
- edges: [],
474
- skills: {
475
- "my-rubric": {
476
- name: "My Rubric",
477
- instruction: "Check for security issues first, then correctness.",
478
- },
479
- },
480
- };
481
- let capturedInstruction = "";
482
- const claude = {
483
- async run(opts) {
484
- capturedInstruction = opts.instruction;
485
- return { status: "success", data: {}, toolCalls: [] };
486
- },
487
- async evaluate() {
488
- return "";
489
- },
490
- };
491
- // No "my-rubric" in the skill map — executor must pull it from workflow.skills
492
- const { results } = await execute(workflow, {}, {
493
- skills: createSkillMap([]),
494
- claude,
495
- config: {},
496
- });
497
- expect(results.get("review")?.status).toBe("success");
498
- expect(capturedInstruction).toContain("## Skill: My Rubric");
499
- expect(capturedInstruction).toContain("Check for security issues first");
500
- });
501
- it("caller-provided skills take precedence over inline workflow.skills", async () => {
502
- const callerSkill = {
503
- id: "shared",
504
- name: "Caller Version",
505
- description: "From caller",
506
- category: "general",
507
- config: {},
508
- tools: [],
509
- instruction: "Caller instruction wins.",
510
- };
511
- const workflow = {
512
- id: "test-precedence",
513
- name: "Precedence",
514
- description: "",
515
- entry: "step",
516
- nodes: {
517
- step: {
518
- name: "Step",
519
- instruction: "Do work.",
520
- skills: ["shared"],
521
- },
522
- },
523
- edges: [],
524
- skills: {
525
- shared: {
526
- name: "Inline Version",
527
- instruction: "Inline instruction should NOT appear.",
528
- },
529
- },
530
- };
531
- let capturedInstruction = "";
532
- const claude = {
533
- async run(opts) {
534
- capturedInstruction = opts.instruction;
535
- return { status: "success", data: {}, toolCalls: [] };
536
- },
537
- async evaluate() {
538
- return "";
539
- },
540
- };
541
- await execute(workflow, {}, {
542
- skills: createSkillMap([callerSkill]),
543
- claude,
544
- config: {},
545
- });
546
- expect(capturedInstruction).toContain("Caller instruction wins.");
547
- expect(capturedInstruction).not.toContain("Inline instruction should NOT appear.");
548
- });
549
- });
550
- // ─── Source resolution phase tests ────────────────────────────────
551
- describe("executor: Source resolution phase", () => {
552
- it("populates trace.sources with node instructions keyed by field path", async () => {
553
- const workflow = {
554
- id: "src-test",
555
- name: "Source test",
556
- description: "",
557
- entry: "only",
558
- nodes: { only: { name: "Only", instruction: "Say hi", skills: [] } },
559
- edges: [],
560
- };
561
- const skills = createSkillMap([]);
562
- const claude = new MockClaude({ responses: { only: { data: {} } } });
563
- const result = await execute(workflow, {}, { skills, claude });
564
- expect(result.trace.sources["nodes.only.instruction"]).toBeDefined();
565
- expect(result.trace.sources["nodes.only.instruction"].content).toBe("Say hi");
566
- expect(result.trace.sources["nodes.only.instruction"].kind).toBe("inline");
567
- });
568
- it("emits workflow:start before sources:resolved (per spec)", async () => {
569
- const workflow = {
570
- id: "evt-test",
571
- name: "Event test",
572
- description: "",
573
- entry: "a",
574
- nodes: { a: { name: "A", instruction: "Do A", skills: [] } },
575
- edges: [],
576
- };
577
- const events = [];
578
- const claude = new MockClaude({ responses: { a: { data: {} } } });
579
- await execute(workflow, {}, {
580
- skills: createSkillMap([]),
581
- claude,
582
- observer: (e) => events.push(e),
583
- });
584
- const startIdx = events.findIndex((e) => e.type === "workflow:start");
585
- const srcIdx = events.findIndex((e) => e.type === "sources:resolved");
586
- expect(startIdx).toBeGreaterThanOrEqual(0);
587
- expect(srcIdx).toBeGreaterThan(startIdx);
588
- });
589
- it("resolves object-form Source (inline tag)", async () => {
590
- const workflow = {
591
- id: "obj-src",
592
- name: "Object source",
593
- description: "",
594
- entry: "only",
595
- nodes: { only: { name: "Only", instruction: { inline: "Hello from inline" }, skills: [] } },
596
- edges: [],
597
- };
598
- const claude = new MockClaude({ responses: { only: { data: {} } } });
599
- const result = await execute(workflow, {}, { skills: createSkillMap([]), claude });
600
- expect(result.trace.sources["nodes.only.instruction"].content).toBe("Hello from inline");
601
- expect(result.trace.sources["nodes.only.instruction"].kind).toBe("inline");
602
- });
603
- });
@@ -1 +0,0 @@
1
- export {};