@sweny-ai/core 0.1.73 → 0.1.75
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/__tests__/executor.test.js +196 -1
- package/dist/__tests__/schema.test.js +68 -1
- package/dist/__tests__/verify.test.d.ts +1 -0
- package/dist/__tests__/verify.test.js +517 -0
- package/dist/executor.js +10 -30
- package/dist/schema.d.ts +284 -0
- package/dist/schema.js +21 -2
- package/dist/types.d.ts +31 -12
- package/dist/verify.d.ts +23 -0
- package/dist/verify.js +309 -0
- package/package.json +2 -2
|
@@ -483,7 +483,7 @@ describe("executor", () => {
|
|
|
483
483
|
const { results } = await execute(verifyWorkflow, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
484
484
|
const r = results.get("create");
|
|
485
485
|
expect(r.status).toBe("failed");
|
|
486
|
-
expect(r.data.error).toMatch(/verify
|
|
486
|
+
expect(r.data.error).toMatch(/verify failed:.*any_tool_called/s);
|
|
487
487
|
expect(r.data.error).toMatch(/linear_create_issue/);
|
|
488
488
|
});
|
|
489
489
|
it("marks node failed when required tool erred (output.error set)", async () => {
|
|
@@ -537,5 +537,200 @@ describe("executor", () => {
|
|
|
537
537
|
expect(results.get("create").status).toBe("failed");
|
|
538
538
|
expect(results.get("create").data.error).toBe("max_turns");
|
|
539
539
|
});
|
|
540
|
+
it("marks node failed when all_tools_called missing a required tool", async () => {
|
|
541
|
+
const wf = {
|
|
542
|
+
id: "wf",
|
|
543
|
+
name: "wf",
|
|
544
|
+
description: "",
|
|
545
|
+
entry: "n",
|
|
546
|
+
nodes: {
|
|
547
|
+
n: {
|
|
548
|
+
name: "N",
|
|
549
|
+
instruction: "Do",
|
|
550
|
+
skills: [],
|
|
551
|
+
verify: { all_tools_called: ["a", "b"] },
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
edges: [],
|
|
555
|
+
};
|
|
556
|
+
const claude = {
|
|
557
|
+
async run() {
|
|
558
|
+
return {
|
|
559
|
+
status: "success",
|
|
560
|
+
data: {},
|
|
561
|
+
toolCalls: [{ tool: "a", input: {}, output: { ok: true } }],
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
async evaluate(opts) {
|
|
565
|
+
return opts.choices[0]?.id;
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
569
|
+
const r = results.get("n");
|
|
570
|
+
expect(r.status).toBe("failed");
|
|
571
|
+
expect(r.data.error).toMatch(/verify failed.*all_tools_called.*\[b\]/s);
|
|
572
|
+
});
|
|
573
|
+
it("marks node failed when no_tool_called was violated", async () => {
|
|
574
|
+
const wf = {
|
|
575
|
+
id: "wf",
|
|
576
|
+
name: "wf",
|
|
577
|
+
description: "",
|
|
578
|
+
entry: "n",
|
|
579
|
+
nodes: {
|
|
580
|
+
n: {
|
|
581
|
+
name: "N",
|
|
582
|
+
instruction: "Do",
|
|
583
|
+
skills: [],
|
|
584
|
+
verify: { no_tool_called: ["force_push"] },
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
edges: [],
|
|
588
|
+
};
|
|
589
|
+
const claude = {
|
|
590
|
+
async run() {
|
|
591
|
+
return {
|
|
592
|
+
status: "success",
|
|
593
|
+
data: {},
|
|
594
|
+
toolCalls: [{ tool: "force_push", input: {}, output: { ok: true } }],
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
async evaluate(opts) {
|
|
598
|
+
return opts.choices[0]?.id;
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
602
|
+
const r = results.get("n");
|
|
603
|
+
expect(r.status).toBe("failed");
|
|
604
|
+
expect(r.data.error).toMatch(/verify failed.*no_tool_called.*force_push/s);
|
|
605
|
+
});
|
|
606
|
+
it("marks node failed when output_required is missing", async () => {
|
|
607
|
+
const wf = {
|
|
608
|
+
id: "wf",
|
|
609
|
+
name: "wf",
|
|
610
|
+
description: "",
|
|
611
|
+
entry: "n",
|
|
612
|
+
nodes: {
|
|
613
|
+
n: {
|
|
614
|
+
name: "N",
|
|
615
|
+
instruction: "Do",
|
|
616
|
+
skills: [],
|
|
617
|
+
verify: { output_required: ["prUrl"] },
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
edges: [],
|
|
621
|
+
};
|
|
622
|
+
const claude = {
|
|
623
|
+
async run() {
|
|
624
|
+
return { status: "success", data: { branch: "main" }, toolCalls: [] };
|
|
625
|
+
},
|
|
626
|
+
async evaluate(opts) {
|
|
627
|
+
return opts.choices[0]?.id;
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
631
|
+
const r = results.get("n");
|
|
632
|
+
expect(r.status).toBe("failed");
|
|
633
|
+
expect(r.data.error).toMatch(/verify failed.*output_required.*'prUrl'/s);
|
|
634
|
+
});
|
|
635
|
+
it("marks node failed when output_matches assertion fails", async () => {
|
|
636
|
+
const wf = {
|
|
637
|
+
id: "wf",
|
|
638
|
+
name: "wf",
|
|
639
|
+
description: "",
|
|
640
|
+
entry: "n",
|
|
641
|
+
nodes: {
|
|
642
|
+
n: {
|
|
643
|
+
name: "N",
|
|
644
|
+
instruction: "Do",
|
|
645
|
+
skills: [],
|
|
646
|
+
verify: { output_matches: [{ path: "branch", matches: "^sweny/" }] },
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
edges: [],
|
|
650
|
+
};
|
|
651
|
+
const claude = {
|
|
652
|
+
async run() {
|
|
653
|
+
return { status: "success", data: { branch: "main" }, toolCalls: [] };
|
|
654
|
+
},
|
|
655
|
+
async evaluate(opts) {
|
|
656
|
+
return opts.choices[0]?.id;
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
660
|
+
const r = results.get("n");
|
|
661
|
+
expect(r.status).toBe("failed");
|
|
662
|
+
expect(r.data.error).toMatch(/verify failed.*output_matches.*'branch'/s);
|
|
663
|
+
});
|
|
664
|
+
it("aggregates multiple verify failures into one error string", async () => {
|
|
665
|
+
const wf = {
|
|
666
|
+
id: "wf",
|
|
667
|
+
name: "wf",
|
|
668
|
+
description: "",
|
|
669
|
+
entry: "n",
|
|
670
|
+
nodes: {
|
|
671
|
+
n: {
|
|
672
|
+
name: "N",
|
|
673
|
+
instruction: "Do",
|
|
674
|
+
skills: [],
|
|
675
|
+
verify: {
|
|
676
|
+
all_tools_called: ["create_pr"],
|
|
677
|
+
output_required: ["prUrl"],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
edges: [],
|
|
682
|
+
};
|
|
683
|
+
const claude = {
|
|
684
|
+
async run() {
|
|
685
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
686
|
+
},
|
|
687
|
+
async evaluate(opts) {
|
|
688
|
+
return opts.choices[0]?.id;
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
692
|
+
const r = results.get("n");
|
|
693
|
+
expect(r.status).toBe("failed");
|
|
694
|
+
expect(r.data.error).toMatch(/all_tools_called/);
|
|
695
|
+
expect(r.data.error).toMatch(/output_required/);
|
|
696
|
+
});
|
|
697
|
+
it("passes a node when every declared check passes", async () => {
|
|
698
|
+
const wf = {
|
|
699
|
+
id: "wf",
|
|
700
|
+
name: "wf",
|
|
701
|
+
description: "",
|
|
702
|
+
entry: "n",
|
|
703
|
+
nodes: {
|
|
704
|
+
n: {
|
|
705
|
+
name: "N",
|
|
706
|
+
instruction: "Do",
|
|
707
|
+
skills: [],
|
|
708
|
+
verify: {
|
|
709
|
+
all_tools_called: ["create_pr"],
|
|
710
|
+
no_tool_called: ["force_push"],
|
|
711
|
+
output_required: ["prUrl"],
|
|
712
|
+
output_matches: [{ path: "prUrl", matches: "^https://" }],
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
edges: [],
|
|
717
|
+
};
|
|
718
|
+
const claude = {
|
|
719
|
+
async run() {
|
|
720
|
+
return {
|
|
721
|
+
status: "success",
|
|
722
|
+
data: { prUrl: "https://github.com/x/y/pull/1" },
|
|
723
|
+
toolCalls: [{ tool: "create_pr", input: {}, output: { ok: true } }],
|
|
724
|
+
};
|
|
725
|
+
},
|
|
726
|
+
async evaluate(opts) {
|
|
727
|
+
return opts.choices[0]?.id;
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
const { results } = await execute(wf, {}, { skills: createSkillMap([]), claude, config: {} });
|
|
731
|
+
const r = results.get("n");
|
|
732
|
+
expect(r.status).toBe("success");
|
|
733
|
+
expect(r.data.error).toBeUndefined();
|
|
734
|
+
});
|
|
540
735
|
});
|
|
541
736
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { workflowZ, nodeZ, edgeZ, skillZ, toolZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "../schema.js";
|
|
2
|
+
import { workflowZ, nodeZ, nodeVerifyZ, edgeZ, skillZ, toolZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "../schema.js";
|
|
3
3
|
import { triageWorkflow, implementWorkflow } from "../workflows/index.js";
|
|
4
4
|
// ─── Test fixtures ───────────────────────────────────────────────
|
|
5
5
|
const validWorkflow = {
|
|
@@ -57,6 +57,73 @@ describe("Zod schemas", () => {
|
|
|
57
57
|
it("rejects verify with empty any_tool_called", () => {
|
|
58
58
|
expect(() => nodeZ.parse({ name: "S", instruction: "I", verify: { any_tool_called: [] } })).toThrow();
|
|
59
59
|
});
|
|
60
|
+
it("accepts all_tools_called", () => {
|
|
61
|
+
const v = nodeVerifyZ.parse({ all_tools_called: ["a", "b"] });
|
|
62
|
+
expect(v.all_tools_called).toEqual(["a", "b"]);
|
|
63
|
+
});
|
|
64
|
+
it("accepts no_tool_called", () => {
|
|
65
|
+
const v = nodeVerifyZ.parse({ no_tool_called: ["force_push"] });
|
|
66
|
+
expect(v.no_tool_called).toEqual(["force_push"]);
|
|
67
|
+
});
|
|
68
|
+
it("accepts output_required", () => {
|
|
69
|
+
const v = nodeVerifyZ.parse({ output_required: ["prUrl", "branch"] });
|
|
70
|
+
expect(v.output_required).toEqual(["prUrl", "branch"]);
|
|
71
|
+
});
|
|
72
|
+
it("accepts output_matches with each operator", () => {
|
|
73
|
+
const v = nodeVerifyZ.parse({
|
|
74
|
+
output_matches: [
|
|
75
|
+
{ path: "a", equals: 1 },
|
|
76
|
+
{ path: "b", in: ["x", "y"] },
|
|
77
|
+
{ path: "c", matches: "^foo" },
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
expect(v.output_matches).toHaveLength(3);
|
|
81
|
+
});
|
|
82
|
+
it("accepts a combination of multiple checks", () => {
|
|
83
|
+
const v = nodeVerifyZ.parse({
|
|
84
|
+
any_tool_called: ["a"],
|
|
85
|
+
output_required: ["x"],
|
|
86
|
+
output_matches: [{ path: "x", equals: 1 }],
|
|
87
|
+
});
|
|
88
|
+
expect(v.any_tool_called).toBeDefined();
|
|
89
|
+
expect(v.output_required).toBeDefined();
|
|
90
|
+
expect(v.output_matches).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it("rejects an output_matches entry with zero operators", () => {
|
|
93
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a" }] })).toThrow();
|
|
94
|
+
});
|
|
95
|
+
it("rejects an output_matches entry with two operators", () => {
|
|
96
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a", equals: 1, in: [1, 2] }] })).toThrow();
|
|
97
|
+
});
|
|
98
|
+
it("rejects an output_matches entry with equals + matches", () => {
|
|
99
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a", equals: 1, matches: "^x$" }] })).toThrow();
|
|
100
|
+
});
|
|
101
|
+
it("rejects an output_matches entry with in + matches", () => {
|
|
102
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a", in: [1, 2], matches: "^x$" }] })).toThrow();
|
|
103
|
+
});
|
|
104
|
+
it("rejects an output_matches entry with all three operators", () => {
|
|
105
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a", equals: 1, in: [1], matches: "^x$" }] })).toThrow();
|
|
106
|
+
});
|
|
107
|
+
it("accepts equals: null as a valid operator value", () => {
|
|
108
|
+
// null IS a valid value to assert equality against; only `undefined` means
|
|
109
|
+
// "not set". Schema must distinguish.
|
|
110
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "a", equals: null }] })).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
it("rejects an output_matches entry with empty path", () => {
|
|
113
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [{ path: "", equals: 1 }] })).toThrow();
|
|
114
|
+
});
|
|
115
|
+
it("rejects empty output_required", () => {
|
|
116
|
+
expect(() => nodeVerifyZ.parse({ output_required: [] })).toThrow();
|
|
117
|
+
});
|
|
118
|
+
it("rejects empty all_tools_called", () => {
|
|
119
|
+
expect(() => nodeVerifyZ.parse({ all_tools_called: [] })).toThrow();
|
|
120
|
+
});
|
|
121
|
+
it("rejects empty no_tool_called", () => {
|
|
122
|
+
expect(() => nodeVerifyZ.parse({ no_tool_called: [] })).toThrow();
|
|
123
|
+
});
|
|
124
|
+
it("rejects empty output_matches array", () => {
|
|
125
|
+
expect(() => nodeVerifyZ.parse({ output_matches: [] })).toThrow();
|
|
126
|
+
});
|
|
60
127
|
});
|
|
61
128
|
describe("edgeZ", () => {
|
|
62
129
|
it("parses a simple edge", () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|