@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.
- package/dist/browser.d.ts +11 -5
- package/dist/browser.js +11 -11
- package/dist/claude.d.ts +20 -0
- package/dist/claude.js +139 -9
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.js +56 -9
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +56 -57
- package/dist/cli/marketplace.js +35 -9
- package/dist/cli/publish.d.ts +10 -0
- package/dist/cli/publish.js +22 -14
- package/dist/executor.js +104 -43
- package/dist/loader.d.ts +40 -0
- package/dist/loader.js +73 -0
- package/dist/mcp-catalog.d.ts +47 -0
- package/dist/mcp-catalog.js +304 -0
- package/dist/mcp.d.ts +6 -9
- package/dist/mcp.js +24 -314
- package/dist/requires.d.ts +7 -0
- package/dist/requires.js +34 -0
- package/dist/retry.d.ts +23 -0
- package/dist/retry.js +72 -0
- package/dist/schema.d.ts +667 -80
- package/dist/schema.js +144 -5
- package/dist/skills/custom-loader.d.ts +40 -0
- package/dist/skills/custom-loader.js +107 -32
- package/dist/skills/index.d.ts +13 -1
- package/dist/skills/index.js +12 -3
- package/dist/studio.js +4 -1
- package/dist/templates.d.ts +29 -8
- package/dist/templates.js +30 -20
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +7 -0
- package/dist/types.d.ts +76 -0
- package/dist/verify.d.ts +1 -0
- package/dist/verify.js +43 -5
- package/package.json +8 -4
- package/dist/__tests__/cascade.test.d.ts +0 -11
- package/dist/__tests__/cascade.test.js +0 -449
- package/dist/__tests__/claude.test.d.ts +0 -1
- package/dist/__tests__/claude.test.js +0 -427
- package/dist/__tests__/custom-loader.test.d.ts +0 -1
- package/dist/__tests__/custom-loader.test.js +0 -116
- package/dist/__tests__/executor.test.d.ts +0 -1
- package/dist/__tests__/executor.test.js +0 -736
- package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/datadog.integration.test.js +0 -23
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
- package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/github.integration.test.js +0 -37
- package/dist/__tests__/integration/harness.d.ts +0 -24
- package/dist/__tests__/integration/harness.js +0 -34
- package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/linear.integration.test.js +0 -15
- package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sentry.integration.test.js +0 -20
- package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/slack.integration.test.js +0 -22
- package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sources.integration.test.js +0 -123
- package/dist/__tests__/publish.test.d.ts +0 -1
- package/dist/__tests__/publish.test.js +0 -180
- package/dist/__tests__/schema.test.d.ts +0 -1
- package/dist/__tests__/schema.test.js +0 -526
- package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
- package/dist/__tests__/skill-config-integration.test.js +0 -204
- package/dist/__tests__/skills-index.test.d.ts +0 -1
- package/dist/__tests__/skills-index.test.js +0 -119
- package/dist/__tests__/skills.test.d.ts +0 -1
- package/dist/__tests__/skills.test.js +0 -565
- package/dist/__tests__/spec-conformance.test.d.ts +0 -18
- package/dist/__tests__/spec-conformance.test.js +0 -1074
- package/dist/__tests__/studio.test.d.ts +0 -1
- package/dist/__tests__/studio.test.js +0 -403
- package/dist/__tests__/templates.test.d.ts +0 -1
- package/dist/__tests__/templates.test.js +0 -49
- package/dist/__tests__/testing.test.d.ts +0 -1
- package/dist/__tests__/testing.test.js +0 -224
- package/dist/__tests__/verify.test.d.ts +0 -1
- package/dist/__tests__/verify.test.js +0 -517
- package/dist/__tests__/version-consistency.test.d.ts +0 -1
- package/dist/__tests__/version-consistency.test.js +0 -312
- package/dist/__tests__/workflow-builder.test.d.ts +0 -1
- package/dist/__tests__/workflow-builder.test.js +0 -110
- package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
- package/dist/__tests__/workflow-yaml.test.js +0 -201
- package/dist/cli/__tests__/diagram.test.d.ts +0 -1
- package/dist/cli/__tests__/diagram.test.js +0 -225
- package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
- package/dist/cli/__tests__/report-cloud.test.js +0 -165
- package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
- package/dist/cli/__tests__/upgrade.test.js +0 -317
- package/dist/cli/__tests__/version-check.test.d.ts +0 -1
- package/dist/cli/__tests__/version-check.test.js +0 -209
- package/dist/cli/config.test.d.ts +0 -1
- package/dist/cli/config.test.js +0 -200
- package/dist/cli/credentials.test.d.ts +0 -1
- package/dist/cli/credentials.test.js +0 -172
- package/dist/cli/e2e.test.d.ts +0 -1
- package/dist/cli/e2e.test.js +0 -690
- package/dist/cli/marketplace.test.d.ts +0 -1
- package/dist/cli/marketplace.test.js +0 -355
- package/dist/cli/new.test.d.ts +0 -1
- package/dist/cli/new.test.js +0 -1043
- package/dist/cli/output.test.d.ts +0 -1
- package/dist/cli/output.test.js +0 -180
- package/dist/cli/publish.test.d.ts +0 -1
- package/dist/cli/publish.test.js +0 -610
- package/dist/cli/renderer.test.d.ts +0 -1
- package/dist/cli/renderer.test.js +0 -302
- package/dist/executor.test.d.ts +0 -1
- package/dist/executor.test.js +0 -603
- package/dist/mcp.test.d.ts +0 -1
- package/dist/mcp.test.js +0 -735
- package/dist/mermaid.test.d.ts +0 -1
- package/dist/mermaid.test.js +0 -256
- package/dist/sources.test.d.ts +0 -1
- package/dist/sources.test.js +0 -265
- package/dist/sources.validation.test.d.ts +0 -12
- package/dist/sources.validation.test.js +0 -380
- package/dist/workflow-builder.test.d.ts +0 -1
- package/dist/workflow-builder.test.js +0 -117
package/dist/executor.test.js
DELETED
|
@@ -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
|
-
});
|
package/dist/mcp.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|