@sweny-ai/core 0.1.77 → 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 (119) hide show
  1. package/dist/browser.d.ts +11 -5
  2. package/dist/browser.js +11 -11
  3. package/dist/claude.d.ts +16 -0
  4. package/dist/claude.js +98 -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/loader.d.ts +40 -0
  13. package/dist/loader.js +73 -0
  14. package/dist/mcp-catalog.d.ts +47 -0
  15. package/dist/mcp-catalog.js +304 -0
  16. package/dist/mcp.d.ts +6 -9
  17. package/dist/mcp.js +24 -314
  18. package/dist/schema.d.ts +183 -151
  19. package/dist/schema.js +49 -7
  20. package/dist/skills/custom-loader.d.ts +40 -0
  21. package/dist/skills/custom-loader.js +107 -32
  22. package/dist/skills/index.d.ts +13 -1
  23. package/dist/skills/index.js +12 -3
  24. package/dist/studio.js +4 -1
  25. package/dist/templates.d.ts +29 -8
  26. package/dist/templates.js +30 -20
  27. package/dist/types.d.ts +12 -0
  28. package/dist/verify.js +42 -4
  29. package/package.json +8 -4
  30. package/dist/__tests__/cascade.test.d.ts +0 -11
  31. package/dist/__tests__/cascade.test.js +0 -452
  32. package/dist/__tests__/claude.test.d.ts +0 -1
  33. package/dist/__tests__/claude.test.js +0 -427
  34. package/dist/__tests__/custom-loader.test.d.ts +0 -1
  35. package/dist/__tests__/custom-loader.test.js +0 -116
  36. package/dist/__tests__/executor.test.d.ts +0 -1
  37. package/dist/__tests__/executor.test.js +0 -1635
  38. package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
  39. package/dist/__tests__/integration/datadog.integration.test.js +0 -23
  40. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
  41. package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
  42. package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
  43. package/dist/__tests__/integration/github.integration.test.js +0 -37
  44. package/dist/__tests__/integration/harness.d.ts +0 -24
  45. package/dist/__tests__/integration/harness.js +0 -34
  46. package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
  47. package/dist/__tests__/integration/linear.integration.test.js +0 -15
  48. package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
  49. package/dist/__tests__/integration/sentry.integration.test.js +0 -20
  50. package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
  51. package/dist/__tests__/integration/slack.integration.test.js +0 -22
  52. package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
  53. package/dist/__tests__/integration/sources.integration.test.js +0 -123
  54. package/dist/__tests__/publish.test.d.ts +0 -1
  55. package/dist/__tests__/publish.test.js +0 -180
  56. package/dist/__tests__/requires.test.d.ts +0 -1
  57. package/dist/__tests__/requires.test.js +0 -82
  58. package/dist/__tests__/retry.test.d.ts +0 -1
  59. package/dist/__tests__/retry.test.js +0 -213
  60. package/dist/__tests__/schema.test.d.ts +0 -1
  61. package/dist/__tests__/schema.test.js +0 -600
  62. package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
  63. package/dist/__tests__/skill-config-integration.test.js +0 -204
  64. package/dist/__tests__/skills-index.test.d.ts +0 -1
  65. package/dist/__tests__/skills-index.test.js +0 -119
  66. package/dist/__tests__/skills.test.d.ts +0 -1
  67. package/dist/__tests__/skills.test.js +0 -565
  68. package/dist/__tests__/spec-conformance.test.d.ts +0 -18
  69. package/dist/__tests__/spec-conformance.test.js +0 -1084
  70. package/dist/__tests__/studio.test.d.ts +0 -1
  71. package/dist/__tests__/studio.test.js +0 -403
  72. package/dist/__tests__/templates.test.d.ts +0 -1
  73. package/dist/__tests__/templates.test.js +0 -49
  74. package/dist/__tests__/testing.test.d.ts +0 -1
  75. package/dist/__tests__/testing.test.js +0 -242
  76. package/dist/__tests__/verify.test.d.ts +0 -1
  77. package/dist/__tests__/verify.test.js +0 -517
  78. package/dist/__tests__/version-consistency.test.d.ts +0 -1
  79. package/dist/__tests__/version-consistency.test.js +0 -312
  80. package/dist/__tests__/workflow-builder.test.d.ts +0 -1
  81. package/dist/__tests__/workflow-builder.test.js +0 -110
  82. package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
  83. package/dist/__tests__/workflow-yaml.test.js +0 -201
  84. package/dist/cli/__tests__/diagram.test.d.ts +0 -1
  85. package/dist/cli/__tests__/diagram.test.js +0 -225
  86. package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
  87. package/dist/cli/__tests__/report-cloud.test.js +0 -165
  88. package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
  89. package/dist/cli/__tests__/upgrade.test.js +0 -317
  90. package/dist/cli/__tests__/version-check.test.d.ts +0 -1
  91. package/dist/cli/__tests__/version-check.test.js +0 -209
  92. package/dist/cli/config.test.d.ts +0 -1
  93. package/dist/cli/config.test.js +0 -200
  94. package/dist/cli/credentials.test.d.ts +0 -1
  95. package/dist/cli/credentials.test.js +0 -172
  96. package/dist/cli/e2e.test.d.ts +0 -1
  97. package/dist/cli/e2e.test.js +0 -690
  98. package/dist/cli/marketplace.test.d.ts +0 -1
  99. package/dist/cli/marketplace.test.js +0 -355
  100. package/dist/cli/new.test.d.ts +0 -1
  101. package/dist/cli/new.test.js +0 -1043
  102. package/dist/cli/output.test.d.ts +0 -1
  103. package/dist/cli/output.test.js +0 -180
  104. package/dist/cli/publish.test.d.ts +0 -1
  105. package/dist/cli/publish.test.js +0 -610
  106. package/dist/cli/renderer.test.d.ts +0 -1
  107. package/dist/cli/renderer.test.js +0 -302
  108. package/dist/executor.test.d.ts +0 -1
  109. package/dist/executor.test.js +0 -618
  110. package/dist/mcp.test.d.ts +0 -1
  111. package/dist/mcp.test.js +0 -735
  112. package/dist/mermaid.test.d.ts +0 -1
  113. package/dist/mermaid.test.js +0 -256
  114. package/dist/sources.test.d.ts +0 -1
  115. package/dist/sources.test.js +0 -265
  116. package/dist/sources.validation.test.d.ts +0 -12
  117. package/dist/sources.validation.test.js +0 -380
  118. package/dist/workflow-builder.test.d.ts +0 -1
  119. package/dist/workflow-builder.test.js +0 -123
@@ -1,618 +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
- async ask() {
357
- return "";
358
- },
359
- };
360
- await execute(workflow, {}, {
361
- skills: createSkillMap([instructionSkill]),
362
- claude,
363
- config: {},
364
- });
365
- expect(capturedInstruction).toContain("## Skill: Code Standards");
366
- expect(capturedInstruction).toContain("Always use camelCase for variable names.");
367
- expect(capturedInstruction).toContain("Write some code.");
368
- });
369
- it("injects multiple skill instructions in array order", async () => {
370
- const skillA = {
371
- id: "skill-a",
372
- name: "Skill A",
373
- description: "First",
374
- category: "general",
375
- config: {},
376
- tools: [],
377
- instruction: "AAA instruction",
378
- };
379
- const skillB = {
380
- id: "skill-b",
381
- name: "Skill B",
382
- description: "Second",
383
- category: "general",
384
- config: {},
385
- tools: [],
386
- instruction: "BBB instruction",
387
- };
388
- const workflow = {
389
- id: "test-multi",
390
- name: "Multi Inject",
391
- description: "",
392
- entry: "step",
393
- nodes: {
394
- step: {
395
- name: "Work",
396
- instruction: "Do the work.",
397
- skills: ["skill-a", "skill-b"],
398
- },
399
- },
400
- edges: [],
401
- };
402
- let capturedInstruction = "";
403
- const claude = {
404
- async run(opts) {
405
- capturedInstruction = opts.instruction;
406
- return { status: "success", data: {}, toolCalls: [] };
407
- },
408
- async evaluate() {
409
- return "";
410
- },
411
- async ask() {
412
- return "";
413
- },
414
- };
415
- await execute(workflow, {}, {
416
- skills: createSkillMap([skillA, skillB]),
417
- claude,
418
- config: {},
419
- });
420
- const posA = capturedInstruction.indexOf("AAA instruction");
421
- const posB = capturedInstruction.indexOf("BBB instruction");
422
- const posBase = capturedInstruction.indexOf("Do the work.");
423
- expect(posA).toBeLessThan(posB);
424
- expect(posB).toBeLessThan(posBase);
425
- });
426
- it("allows instruction-only nodes (no tools, no error)", async () => {
427
- const instructionSkill = {
428
- id: "rubric",
429
- name: "Evaluation Rubric",
430
- description: "Scoring criteria",
431
- category: "general",
432
- config: {},
433
- tools: [],
434
- instruction: "Score quality 1-5.",
435
- };
436
- const workflow = {
437
- id: "test-notools",
438
- name: "No Tools",
439
- description: "",
440
- entry: "judge",
441
- nodes: {
442
- judge: {
443
- name: "Judge",
444
- instruction: "Evaluate the output.",
445
- skills: ["rubric"],
446
- },
447
- },
448
- edges: [],
449
- };
450
- const claude = {
451
- async run() {
452
- return { status: "success", data: { score: 4 }, toolCalls: [] };
453
- },
454
- async evaluate() {
455
- return "";
456
- },
457
- async ask() {
458
- return "";
459
- },
460
- };
461
- // Should NOT throw "none are configured"
462
- const { results } = await execute(workflow, {}, {
463
- skills: createSkillMap([instructionSkill]),
464
- claude,
465
- config: {},
466
- });
467
- expect(results.get("judge")?.status).toBe("success");
468
- });
469
- it("wires inline workflow.skills into the executor automatically", async () => {
470
- const workflow = {
471
- id: "test-inline",
472
- name: "Inline Skills",
473
- description: "",
474
- entry: "review",
475
- nodes: {
476
- review: {
477
- name: "Review",
478
- instruction: "Review the code.",
479
- skills: ["my-rubric"],
480
- },
481
- },
482
- edges: [],
483
- skills: {
484
- "my-rubric": {
485
- name: "My Rubric",
486
- instruction: "Check for security issues first, then correctness.",
487
- },
488
- },
489
- };
490
- let capturedInstruction = "";
491
- const claude = {
492
- async run(opts) {
493
- capturedInstruction = opts.instruction;
494
- return { status: "success", data: {}, toolCalls: [] };
495
- },
496
- async evaluate() {
497
- return "";
498
- },
499
- async ask() {
500
- return "";
501
- },
502
- };
503
- // No "my-rubric" in the skill map — executor must pull it from workflow.skills
504
- const { results } = await execute(workflow, {}, {
505
- skills: createSkillMap([]),
506
- claude,
507
- config: {},
508
- });
509
- expect(results.get("review")?.status).toBe("success");
510
- expect(capturedInstruction).toContain("## Skill: My Rubric");
511
- expect(capturedInstruction).toContain("Check for security issues first");
512
- });
513
- it("caller-provided skills take precedence over inline workflow.skills", async () => {
514
- const callerSkill = {
515
- id: "shared",
516
- name: "Caller Version",
517
- description: "From caller",
518
- category: "general",
519
- config: {},
520
- tools: [],
521
- instruction: "Caller instruction wins.",
522
- };
523
- const workflow = {
524
- id: "test-precedence",
525
- name: "Precedence",
526
- description: "",
527
- entry: "step",
528
- nodes: {
529
- step: {
530
- name: "Step",
531
- instruction: "Do work.",
532
- skills: ["shared"],
533
- },
534
- },
535
- edges: [],
536
- skills: {
537
- shared: {
538
- name: "Inline Version",
539
- instruction: "Inline instruction should NOT appear.",
540
- },
541
- },
542
- };
543
- let capturedInstruction = "";
544
- const claude = {
545
- async run(opts) {
546
- capturedInstruction = opts.instruction;
547
- return { status: "success", data: {}, toolCalls: [] };
548
- },
549
- async evaluate() {
550
- return "";
551
- },
552
- async ask() {
553
- return "";
554
- },
555
- };
556
- await execute(workflow, {}, {
557
- skills: createSkillMap([callerSkill]),
558
- claude,
559
- config: {},
560
- });
561
- expect(capturedInstruction).toContain("Caller instruction wins.");
562
- expect(capturedInstruction).not.toContain("Inline instruction should NOT appear.");
563
- });
564
- });
565
- // ─── Source resolution phase tests ────────────────────────────────
566
- describe("executor: Source resolution phase", () => {
567
- it("populates trace.sources with node instructions keyed by field path", async () => {
568
- const workflow = {
569
- id: "src-test",
570
- name: "Source test",
571
- description: "",
572
- entry: "only",
573
- nodes: { only: { name: "Only", instruction: "Say hi", skills: [] } },
574
- edges: [],
575
- };
576
- const skills = createSkillMap([]);
577
- const claude = new MockClaude({ responses: { only: { data: {} } } });
578
- const result = await execute(workflow, {}, { skills, claude });
579
- expect(result.trace.sources["nodes.only.instruction"]).toBeDefined();
580
- expect(result.trace.sources["nodes.only.instruction"].content).toBe("Say hi");
581
- expect(result.trace.sources["nodes.only.instruction"].kind).toBe("inline");
582
- });
583
- it("emits workflow:start before sources:resolved (per spec)", async () => {
584
- const workflow = {
585
- id: "evt-test",
586
- name: "Event test",
587
- description: "",
588
- entry: "a",
589
- nodes: { a: { name: "A", instruction: "Do A", skills: [] } },
590
- edges: [],
591
- };
592
- const events = [];
593
- const claude = new MockClaude({ responses: { a: { data: {} } } });
594
- await execute(workflow, {}, {
595
- skills: createSkillMap([]),
596
- claude,
597
- observer: (e) => events.push(e),
598
- });
599
- const startIdx = events.findIndex((e) => e.type === "workflow:start");
600
- const srcIdx = events.findIndex((e) => e.type === "sources:resolved");
601
- expect(startIdx).toBeGreaterThanOrEqual(0);
602
- expect(srcIdx).toBeGreaterThan(startIdx);
603
- });
604
- it("resolves object-form Source (inline tag)", async () => {
605
- const workflow = {
606
- id: "obj-src",
607
- name: "Object source",
608
- description: "",
609
- entry: "only",
610
- nodes: { only: { name: "Only", instruction: { inline: "Hello from inline" }, skills: [] } },
611
- edges: [],
612
- };
613
- const claude = new MockClaude({ responses: { only: { data: {} } } });
614
- const result = await execute(workflow, {}, { skills: createSkillMap([]), claude });
615
- expect(result.trace.sources["nodes.only.instruction"].content).toBe("Hello from inline");
616
- expect(result.trace.sources["nodes.only.instruction"].kind).toBe("inline");
617
- });
618
- });
@@ -1 +0,0 @@
1
- export {};