@sweny-ai/core 0.1.51 → 0.1.52
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__/publish.test.d.ts +1 -0
- package/dist/__tests__/publish.test.js +146 -0
- package/dist/__tests__/schema.test.js +156 -1
- package/dist/__tests__/skills.test.js +135 -4
- package/dist/__tests__/templates.test.d.ts +1 -0
- package/dist/__tests__/templates.test.js +49 -0
- package/dist/__tests__/version-consistency.test.js +2 -2
- package/dist/cli/init.d.ts +27 -4
- package/dist/cli/init.js +158 -197
- package/dist/cli/init.test.js +127 -78
- package/dist/cli/main.js +2 -0
- package/dist/cli/publish.d.ts +34 -0
- package/dist/cli/publish.js +368 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.js +181 -0
- package/dist/executor.js +63 -20
- package/dist/executor.test.js +233 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +8 -0
- package/dist/mcp.test.js +67 -0
- package/dist/schema.d.ts +428 -18
- package/dist/schema.js +71 -4
- package/dist/skills/custom-loader.d.ts +12 -12
- package/dist/skills/custom-loader.js +105 -50
- package/dist/types.d.ts +18 -1
- package/dist/workflows/browser.js +3 -3
- package/package.json +6 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starter workflow templates offered during `sweny init`.
|
|
3
|
+
*
|
|
4
|
+
* Each template is a valid SWEny workflow YAML. Templates are self-contained
|
|
5
|
+
* strings — no filesystem reads needed, works from npm install.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkflowTemplate {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
yaml: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const WORKFLOW_TEMPLATES: WorkflowTemplate[];
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starter workflow templates offered during `sweny init`.
|
|
3
|
+
*
|
|
4
|
+
* Each template is a valid SWEny workflow YAML. Templates are self-contained
|
|
5
|
+
* strings — no filesystem reads needed, works from npm install.
|
|
6
|
+
*/
|
|
7
|
+
export const WORKFLOW_TEMPLATES = [
|
|
8
|
+
{
|
|
9
|
+
id: "pr-review",
|
|
10
|
+
name: "PR Review Bot",
|
|
11
|
+
description: "Automated code review on pull requests",
|
|
12
|
+
yaml: `id: pr-review
|
|
13
|
+
name: PR Review Bot
|
|
14
|
+
description: Review pull requests for code quality, security issues, and best practices.
|
|
15
|
+
entry: fetch-diff
|
|
16
|
+
|
|
17
|
+
nodes:
|
|
18
|
+
fetch-diff:
|
|
19
|
+
name: Fetch PR Changes
|
|
20
|
+
instruction: |
|
|
21
|
+
Fetch the pull request diff. Identify which files changed,
|
|
22
|
+
what was added, modified, or removed. Summarize the scope
|
|
23
|
+
of the change.
|
|
24
|
+
skills: [github]
|
|
25
|
+
|
|
26
|
+
review-code:
|
|
27
|
+
name: Review Code
|
|
28
|
+
instruction: |
|
|
29
|
+
Review the code changes for:
|
|
30
|
+
- Security vulnerabilities (injection, auth issues, secrets)
|
|
31
|
+
- Logic errors and edge cases
|
|
32
|
+
- Code style and readability
|
|
33
|
+
- Test coverage gaps
|
|
34
|
+
|
|
35
|
+
Be specific — reference file names and line numbers.
|
|
36
|
+
Categorize findings as critical, important, or minor.
|
|
37
|
+
skills: [github]
|
|
38
|
+
|
|
39
|
+
post-review:
|
|
40
|
+
name: Post Review Comment
|
|
41
|
+
instruction: |
|
|
42
|
+
Post a structured review comment on the pull request.
|
|
43
|
+
Lead with a summary, then list findings by severity.
|
|
44
|
+
If no issues found, approve with a brief note.
|
|
45
|
+
skills: [github]
|
|
46
|
+
|
|
47
|
+
edges:
|
|
48
|
+
- from: fetch-diff
|
|
49
|
+
to: review-code
|
|
50
|
+
- from: review-code
|
|
51
|
+
to: post-review
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "issue-triage",
|
|
56
|
+
name: "Issue Triage",
|
|
57
|
+
description: "Classify, prioritize, and label incoming issues",
|
|
58
|
+
yaml: `id: issue-triage
|
|
59
|
+
name: Issue Triage
|
|
60
|
+
description: Automatically classify, prioritize, and label new issues.
|
|
61
|
+
entry: classify
|
|
62
|
+
|
|
63
|
+
nodes:
|
|
64
|
+
classify:
|
|
65
|
+
name: Classify Issue
|
|
66
|
+
instruction: |
|
|
67
|
+
Read the issue title and body. Classify it as one of:
|
|
68
|
+
- bug: Something is broken
|
|
69
|
+
- feature: A new capability request
|
|
70
|
+
- question: Asking for help or clarification
|
|
71
|
+
- chore: Maintenance, refactoring, docs
|
|
72
|
+
|
|
73
|
+
Also assess priority:
|
|
74
|
+
- P0: Production is down or data loss
|
|
75
|
+
- P1: Major feature broken, workaround exists
|
|
76
|
+
- P2: Minor issue, low impact
|
|
77
|
+
- P3: Nice to have, no urgency
|
|
78
|
+
skills: [github]
|
|
79
|
+
|
|
80
|
+
label-and-assign:
|
|
81
|
+
name: Apply Labels
|
|
82
|
+
instruction: |
|
|
83
|
+
Based on the classification, apply the appropriate labels
|
|
84
|
+
to the issue (e.g. "bug", "P1", "needs-triage").
|
|
85
|
+
Add a comment explaining the classification rationale.
|
|
86
|
+
skills: [github]
|
|
87
|
+
|
|
88
|
+
edges:
|
|
89
|
+
- from: classify
|
|
90
|
+
to: label-and-assign
|
|
91
|
+
`,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "security-scan",
|
|
95
|
+
name: "Security Audit",
|
|
96
|
+
description: "Scan code and dependencies for security issues",
|
|
97
|
+
yaml: `id: security-scan
|
|
98
|
+
name: Security Audit
|
|
99
|
+
description: Scan repository code and dependencies for security vulnerabilities.
|
|
100
|
+
entry: scan-code
|
|
101
|
+
|
|
102
|
+
nodes:
|
|
103
|
+
scan-code:
|
|
104
|
+
name: Scan Code for Secrets
|
|
105
|
+
instruction: |
|
|
106
|
+
Search the repository for exposed secrets, API keys,
|
|
107
|
+
passwords, and tokens in code, config files, and
|
|
108
|
+
environment templates. Check for common patterns:
|
|
109
|
+
hardcoded credentials, leaked keys, insecure defaults.
|
|
110
|
+
skills: [github]
|
|
111
|
+
|
|
112
|
+
scan-deps:
|
|
113
|
+
name: Scan Dependencies
|
|
114
|
+
instruction: |
|
|
115
|
+
Review dependency manifests (package.json, requirements.txt,
|
|
116
|
+
go.mod, etc.) for known vulnerable versions. Check for
|
|
117
|
+
outdated packages with security advisories.
|
|
118
|
+
skills: [github]
|
|
119
|
+
|
|
120
|
+
compile-report:
|
|
121
|
+
name: Compile Security Report
|
|
122
|
+
instruction: |
|
|
123
|
+
Compile findings from the code and dependency scans into
|
|
124
|
+
a structured security report. Categorize by severity
|
|
125
|
+
(critical, high, medium, low). Include remediation steps
|
|
126
|
+
for each finding.
|
|
127
|
+
skills: [github]
|
|
128
|
+
|
|
129
|
+
edges:
|
|
130
|
+
- from: scan-code
|
|
131
|
+
to: scan-deps
|
|
132
|
+
- from: scan-deps
|
|
133
|
+
to: compile-report
|
|
134
|
+
`,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "release-notes",
|
|
138
|
+
name: "Release Notes",
|
|
139
|
+
description: "Generate release notes from commits and PRs",
|
|
140
|
+
yaml: `id: release-notes
|
|
141
|
+
name: Release Notes Generator
|
|
142
|
+
description: Generate structured release notes from recent commits and merged PRs.
|
|
143
|
+
entry: gather
|
|
144
|
+
|
|
145
|
+
nodes:
|
|
146
|
+
gather:
|
|
147
|
+
name: Gather Changes
|
|
148
|
+
instruction: |
|
|
149
|
+
List all commits and merged pull requests since the last
|
|
150
|
+
release tag. For each, extract: title, author, PR number,
|
|
151
|
+
and a brief description of what changed.
|
|
152
|
+
skills: [github]
|
|
153
|
+
|
|
154
|
+
categorize:
|
|
155
|
+
name: Categorize Changes
|
|
156
|
+
instruction: |
|
|
157
|
+
Group the changes into categories:
|
|
158
|
+
- Features: New capabilities
|
|
159
|
+
- Fixes: Bug fixes
|
|
160
|
+
- Improvements: Performance, refactoring, DX
|
|
161
|
+
- Breaking Changes: Anything that requires migration
|
|
162
|
+
|
|
163
|
+
Flag any breaking changes prominently.
|
|
164
|
+
skills: [github]
|
|
165
|
+
|
|
166
|
+
write-notes:
|
|
167
|
+
name: Write Release Notes
|
|
168
|
+
instruction: |
|
|
169
|
+
Write polished release notes in markdown. Lead with
|
|
170
|
+
highlights, then list changes by category. Credit
|
|
171
|
+
contributors. Keep it concise but informative.
|
|
172
|
+
skills: [github]
|
|
173
|
+
|
|
174
|
+
edges:
|
|
175
|
+
- from: gather
|
|
176
|
+
to: categorize
|
|
177
|
+
- from: categorize
|
|
178
|
+
to: write-notes
|
|
179
|
+
`,
|
|
180
|
+
},
|
|
181
|
+
];
|
package/dist/executor.js
CHANGED
|
@@ -21,7 +21,11 @@ import { consoleLogger } from "./types.js";
|
|
|
21
21
|
* - `trace`: full ordered execution trace including loops and routing decisions
|
|
22
22
|
*/
|
|
23
23
|
export async function execute(workflow, input, options) {
|
|
24
|
-
const {
|
|
24
|
+
const { claude, observer } = options;
|
|
25
|
+
// Merge inline workflow skills into the skill map so they resolve at runtime.
|
|
26
|
+
// Inline skills (instruction/mcp only) become Skill objects with empty tools/config.
|
|
27
|
+
// The caller's skill map takes precedence — inline skills only fill gaps.
|
|
28
|
+
const skills = mergeInlineSkills(options.skills, workflow.skills);
|
|
25
29
|
const config = resolveConfig(skills, options.config);
|
|
26
30
|
const logger = options.logger ?? consoleLogger;
|
|
27
31
|
const results = new Map();
|
|
@@ -41,11 +45,13 @@ export async function execute(workflow, input, options) {
|
|
|
41
45
|
logger.info(`→ ${node.name}`, { node: currentId });
|
|
42
46
|
// Gather tools from the node's skills
|
|
43
47
|
const tools = resolveTools(node.skills, skills);
|
|
48
|
+
const skillInstructions = resolveSkillInstructions(node.skills, skills);
|
|
44
49
|
// Runtime guard: if this node declares skills but none resolved, the node
|
|
45
50
|
// cannot do its job (e.g. "create a Linear issue" with no linear skill).
|
|
46
51
|
// The startup validate() warns about this possibility, but only throw when
|
|
47
52
|
// the node is actually reached — unreachable nodes with missing skills are fine.
|
|
48
|
-
|
|
53
|
+
// Instruction-only skills (no tools) are valid — they inject context into the prompt.
|
|
54
|
+
if (node.skills.length > 0 && tools.length === 0 && skillInstructions.length === 0) {
|
|
49
55
|
throw new Error(`Node "${currentId}" requires skills [${node.skills.join(", ")}] but none are configured. ` +
|
|
50
56
|
`Set the required environment variables and try again.`);
|
|
51
57
|
}
|
|
@@ -66,7 +72,7 @@ export async function execute(workflow, input, options) {
|
|
|
66
72
|
},
|
|
67
73
|
}));
|
|
68
74
|
// Prepend rules and context to instruction if provided
|
|
69
|
-
const instruction = buildNodeInstruction(node.instruction, input);
|
|
75
|
+
const instruction = buildNodeInstruction(node.instruction, input, skillInstructions);
|
|
70
76
|
// Run Claude on this node
|
|
71
77
|
const result = await claude.run({
|
|
72
78
|
instruction,
|
|
@@ -116,30 +122,35 @@ export async function execute(workflow, input, options) {
|
|
|
116
122
|
* Rules get "You MUST follow" framing; context gets "Background" framing.
|
|
117
123
|
* Falls back to legacy `additionalContext` if rules/context aren't set.
|
|
118
124
|
*/
|
|
119
|
-
function buildNodeInstruction(baseInstruction, input) {
|
|
125
|
+
function buildNodeInstruction(baseInstruction, input, skillInstructions) {
|
|
120
126
|
const inp = input;
|
|
121
|
-
if (!inp)
|
|
122
|
-
return baseInstruction;
|
|
123
127
|
const sections = [];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
if (inp) {
|
|
129
|
+
const rules = typeof inp.rules === "string" && inp.rules ? inp.rules : "";
|
|
130
|
+
const context = typeof inp.context === "string" && inp.context ? inp.context : "";
|
|
131
|
+
if (rules) {
|
|
132
|
+
sections.push(`## Rules — You MUST Follow These\n\n${rules}`);
|
|
133
|
+
}
|
|
134
|
+
if (context) {
|
|
135
|
+
sections.push(`## Background Context\n\n${context}`);
|
|
136
|
+
}
|
|
137
|
+
// Legacy fallback
|
|
138
|
+
if (sections.length === 0) {
|
|
139
|
+
const legacy = typeof inp.additionalContext === "string" ? inp.additionalContext : "";
|
|
140
|
+
if (legacy) {
|
|
141
|
+
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
132
144
|
}
|
|
133
|
-
//
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
145
|
+
// Skill instructions — injected between context and base instruction
|
|
146
|
+
if (skillInstructions && skillInstructions.length > 0) {
|
|
147
|
+
for (const { name, instruction } of skillInstructions) {
|
|
148
|
+
sections.push(`## Skill: ${name}\n\n${instruction}`);
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
if (sections.length === 0)
|
|
141
152
|
return baseInstruction;
|
|
142
|
-
return `${sections.join("\n\n")}\n\n---\n\n${baseInstruction}`;
|
|
153
|
+
return `${sections.join("\n\n---\n\n")}\n\n---\n\n${baseInstruction}`;
|
|
143
154
|
}
|
|
144
155
|
/**
|
|
145
156
|
* Call observer without letting exceptions crash the workflow.
|
|
@@ -157,12 +168,44 @@ function safeObserve(observer, event, logger) {
|
|
|
157
168
|
(logger ?? consoleLogger).warn(`Observer error (non-fatal): ${err.message}`);
|
|
158
169
|
}
|
|
159
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Merge inline workflow skill definitions into the skill map.
|
|
173
|
+
* Inline skills (from workflow.skills) become Skill objects with empty tools/config.
|
|
174
|
+
* The caller's skill map takes precedence — inline skills only fill gaps.
|
|
175
|
+
*/
|
|
176
|
+
function mergeInlineSkills(skills, inlineSkills) {
|
|
177
|
+
if (!inlineSkills || Object.keys(inlineSkills).length === 0)
|
|
178
|
+
return skills;
|
|
179
|
+
const merged = new Map(skills);
|
|
180
|
+
for (const [id, def] of Object.entries(inlineSkills)) {
|
|
181
|
+
if (merged.has(id))
|
|
182
|
+
continue; // caller-provided skill takes precedence
|
|
183
|
+
merged.set(id, {
|
|
184
|
+
id,
|
|
185
|
+
name: def.name ?? id,
|
|
186
|
+
description: def.description ?? `Inline skill: ${id}`,
|
|
187
|
+
category: "general",
|
|
188
|
+
config: {},
|
|
189
|
+
tools: [],
|
|
190
|
+
instruction: def.instruction,
|
|
191
|
+
mcp: def.mcp,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return merged;
|
|
195
|
+
}
|
|
160
196
|
function resolveTools(skillIds, skills) {
|
|
161
197
|
return skillIds
|
|
162
198
|
.map((id) => skills.get(id))
|
|
163
199
|
.filter((s) => s != null)
|
|
164
200
|
.flatMap((s) => s.tools);
|
|
165
201
|
}
|
|
202
|
+
/** Collect instruction strings from skills that have them, in array order. */
|
|
203
|
+
function resolveSkillInstructions(skillIds, skills) {
|
|
204
|
+
return skillIds
|
|
205
|
+
.map((id) => skills.get(id))
|
|
206
|
+
.filter((s) => s != null && s.instruction != null)
|
|
207
|
+
.map((s) => ({ name: s.name, instruction: s.instruction }));
|
|
208
|
+
}
|
|
166
209
|
/**
|
|
167
210
|
* Resolve config values: check explicit overrides first, then env vars.
|
|
168
211
|
* Throws if a required field is missing.
|
package/dist/executor.test.js
CHANGED
|
@@ -313,3 +313,236 @@ describe("file skill", () => {
|
|
|
313
313
|
expect(existsSync(path.join(outputDir, "deep/nested/file.json"))).toBe(true);
|
|
314
314
|
});
|
|
315
315
|
});
|
|
316
|
+
// ─── Skill instruction injection tests ──────────────────────────
|
|
317
|
+
describe("skill instruction injection", () => {
|
|
318
|
+
let outputDir;
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
outputDir = freshDir("skill-inject");
|
|
321
|
+
});
|
|
322
|
+
it("injects skill instructions into the prompt", async () => {
|
|
323
|
+
const instructionSkill = {
|
|
324
|
+
id: "code-standards",
|
|
325
|
+
name: "Code Standards",
|
|
326
|
+
description: "Team conventions",
|
|
327
|
+
category: "general",
|
|
328
|
+
config: {},
|
|
329
|
+
tools: [],
|
|
330
|
+
instruction: "Always use camelCase for variable names.",
|
|
331
|
+
};
|
|
332
|
+
const workflow = {
|
|
333
|
+
id: "test-inject",
|
|
334
|
+
name: "Inject Test",
|
|
335
|
+
description: "",
|
|
336
|
+
entry: "step",
|
|
337
|
+
nodes: {
|
|
338
|
+
step: {
|
|
339
|
+
name: "Do Work",
|
|
340
|
+
instruction: "Write some code.",
|
|
341
|
+
skills: ["code-standards"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
edges: [],
|
|
345
|
+
};
|
|
346
|
+
let capturedInstruction = "";
|
|
347
|
+
const claude = {
|
|
348
|
+
async run(opts) {
|
|
349
|
+
capturedInstruction = opts.instruction;
|
|
350
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
351
|
+
},
|
|
352
|
+
async evaluate() {
|
|
353
|
+
return "";
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
await execute(workflow, {}, {
|
|
357
|
+
skills: createSkillMap([instructionSkill]),
|
|
358
|
+
claude,
|
|
359
|
+
config: {},
|
|
360
|
+
});
|
|
361
|
+
expect(capturedInstruction).toContain("## Skill: Code Standards");
|
|
362
|
+
expect(capturedInstruction).toContain("Always use camelCase for variable names.");
|
|
363
|
+
expect(capturedInstruction).toContain("Write some code.");
|
|
364
|
+
});
|
|
365
|
+
it("injects multiple skill instructions in array order", async () => {
|
|
366
|
+
const skillA = {
|
|
367
|
+
id: "skill-a",
|
|
368
|
+
name: "Skill A",
|
|
369
|
+
description: "First",
|
|
370
|
+
category: "general",
|
|
371
|
+
config: {},
|
|
372
|
+
tools: [],
|
|
373
|
+
instruction: "AAA instruction",
|
|
374
|
+
};
|
|
375
|
+
const skillB = {
|
|
376
|
+
id: "skill-b",
|
|
377
|
+
name: "Skill B",
|
|
378
|
+
description: "Second",
|
|
379
|
+
category: "general",
|
|
380
|
+
config: {},
|
|
381
|
+
tools: [],
|
|
382
|
+
instruction: "BBB instruction",
|
|
383
|
+
};
|
|
384
|
+
const workflow = {
|
|
385
|
+
id: "test-multi",
|
|
386
|
+
name: "Multi Inject",
|
|
387
|
+
description: "",
|
|
388
|
+
entry: "step",
|
|
389
|
+
nodes: {
|
|
390
|
+
step: {
|
|
391
|
+
name: "Work",
|
|
392
|
+
instruction: "Do the work.",
|
|
393
|
+
skills: ["skill-a", "skill-b"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
edges: [],
|
|
397
|
+
};
|
|
398
|
+
let capturedInstruction = "";
|
|
399
|
+
const claude = {
|
|
400
|
+
async run(opts) {
|
|
401
|
+
capturedInstruction = opts.instruction;
|
|
402
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
403
|
+
},
|
|
404
|
+
async evaluate() {
|
|
405
|
+
return "";
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
await execute(workflow, {}, {
|
|
409
|
+
skills: createSkillMap([skillA, skillB]),
|
|
410
|
+
claude,
|
|
411
|
+
config: {},
|
|
412
|
+
});
|
|
413
|
+
const posA = capturedInstruction.indexOf("AAA instruction");
|
|
414
|
+
const posB = capturedInstruction.indexOf("BBB instruction");
|
|
415
|
+
const posBase = capturedInstruction.indexOf("Do the work.");
|
|
416
|
+
expect(posA).toBeLessThan(posB);
|
|
417
|
+
expect(posB).toBeLessThan(posBase);
|
|
418
|
+
});
|
|
419
|
+
it("allows instruction-only nodes (no tools, no error)", async () => {
|
|
420
|
+
const instructionSkill = {
|
|
421
|
+
id: "rubric",
|
|
422
|
+
name: "Evaluation Rubric",
|
|
423
|
+
description: "Scoring criteria",
|
|
424
|
+
category: "general",
|
|
425
|
+
config: {},
|
|
426
|
+
tools: [],
|
|
427
|
+
instruction: "Score quality 1-5.",
|
|
428
|
+
};
|
|
429
|
+
const workflow = {
|
|
430
|
+
id: "test-notools",
|
|
431
|
+
name: "No Tools",
|
|
432
|
+
description: "",
|
|
433
|
+
entry: "judge",
|
|
434
|
+
nodes: {
|
|
435
|
+
judge: {
|
|
436
|
+
name: "Judge",
|
|
437
|
+
instruction: "Evaluate the output.",
|
|
438
|
+
skills: ["rubric"],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
edges: [],
|
|
442
|
+
};
|
|
443
|
+
const claude = {
|
|
444
|
+
async run() {
|
|
445
|
+
return { status: "success", data: { score: 4 }, toolCalls: [] };
|
|
446
|
+
},
|
|
447
|
+
async evaluate() {
|
|
448
|
+
return "";
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
// Should NOT throw "none are configured"
|
|
452
|
+
const { results } = await execute(workflow, {}, {
|
|
453
|
+
skills: createSkillMap([instructionSkill]),
|
|
454
|
+
claude,
|
|
455
|
+
config: {},
|
|
456
|
+
});
|
|
457
|
+
expect(results.get("judge")?.status).toBe("success");
|
|
458
|
+
});
|
|
459
|
+
it("wires inline workflow.skills into the executor automatically", async () => {
|
|
460
|
+
const workflow = {
|
|
461
|
+
id: "test-inline",
|
|
462
|
+
name: "Inline Skills",
|
|
463
|
+
description: "",
|
|
464
|
+
entry: "review",
|
|
465
|
+
nodes: {
|
|
466
|
+
review: {
|
|
467
|
+
name: "Review",
|
|
468
|
+
instruction: "Review the code.",
|
|
469
|
+
skills: ["my-rubric"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
edges: [],
|
|
473
|
+
skills: {
|
|
474
|
+
"my-rubric": {
|
|
475
|
+
name: "My Rubric",
|
|
476
|
+
instruction: "Check for security issues first, then correctness.",
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
let capturedInstruction = "";
|
|
481
|
+
const claude = {
|
|
482
|
+
async run(opts) {
|
|
483
|
+
capturedInstruction = opts.instruction;
|
|
484
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
485
|
+
},
|
|
486
|
+
async evaluate() {
|
|
487
|
+
return "";
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
// No "my-rubric" in the skill map — executor must pull it from workflow.skills
|
|
491
|
+
const { results } = await execute(workflow, {}, {
|
|
492
|
+
skills: createSkillMap([]),
|
|
493
|
+
claude,
|
|
494
|
+
config: {},
|
|
495
|
+
});
|
|
496
|
+
expect(results.get("review")?.status).toBe("success");
|
|
497
|
+
expect(capturedInstruction).toContain("## Skill: My Rubric");
|
|
498
|
+
expect(capturedInstruction).toContain("Check for security issues first");
|
|
499
|
+
});
|
|
500
|
+
it("caller-provided skills take precedence over inline workflow.skills", async () => {
|
|
501
|
+
const callerSkill = {
|
|
502
|
+
id: "shared",
|
|
503
|
+
name: "Caller Version",
|
|
504
|
+
description: "From caller",
|
|
505
|
+
category: "general",
|
|
506
|
+
config: {},
|
|
507
|
+
tools: [],
|
|
508
|
+
instruction: "Caller instruction wins.",
|
|
509
|
+
};
|
|
510
|
+
const workflow = {
|
|
511
|
+
id: "test-precedence",
|
|
512
|
+
name: "Precedence",
|
|
513
|
+
description: "",
|
|
514
|
+
entry: "step",
|
|
515
|
+
nodes: {
|
|
516
|
+
step: {
|
|
517
|
+
name: "Step",
|
|
518
|
+
instruction: "Do work.",
|
|
519
|
+
skills: ["shared"],
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
edges: [],
|
|
523
|
+
skills: {
|
|
524
|
+
shared: {
|
|
525
|
+
name: "Inline Version",
|
|
526
|
+
instruction: "Inline instruction should NOT appear.",
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
let capturedInstruction = "";
|
|
531
|
+
const claude = {
|
|
532
|
+
async run(opts) {
|
|
533
|
+
capturedInstruction = opts.instruction;
|
|
534
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
535
|
+
},
|
|
536
|
+
async evaluate() {
|
|
537
|
+
return "";
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
await execute(workflow, {}, {
|
|
541
|
+
skills: createSkillMap([callerSkill]),
|
|
542
|
+
claude,
|
|
543
|
+
config: {},
|
|
544
|
+
});
|
|
545
|
+
expect(capturedInstruction).toContain("Caller instruction wins.");
|
|
546
|
+
expect(capturedInstruction).not.toContain("Inline instruction should NOT appear.");
|
|
547
|
+
});
|
|
548
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* })
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
export type { Skill, SkillCategory, Tool, ToolContext, ConfigField, JSONSchema, Workflow, Node, Edge, NodeResult, ToolCall, ExecutionEvent, Observer, Claude, Logger, TraceStep, TraceEdge, ExecutionTrace, ExecutionResult, } from "./types.js";
|
|
24
|
+
export type { Skill, SkillCategory, SkillDefinition, Tool, ToolContext, ConfigField, JSONSchema, Workflow, Node, Edge, NodeResult, ToolCall, ExecutionEvent, Observer, Claude, Logger, TraceStep, TraceEdge, ExecutionTrace, ExecutionResult, } from "./types.js";
|
|
25
25
|
export { consoleLogger } from "./types.js";
|
|
26
26
|
export { execute } from "./executor.js";
|
|
27
27
|
export type { ExecuteOptions } from "./executor.js";
|
|
@@ -29,11 +29,11 @@ export { ClaudeClient } from "./claude.js";
|
|
|
29
29
|
export type { ClaudeClientOptions } from "./claude.js";
|
|
30
30
|
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
export type { SkillValidationResult } from "./skills/index.js";
|
|
32
|
-
export { loadCustomSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
|
-
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
32
|
+
export { loadCustomSkills, discoverSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
|
+
export { workflowZ, nodeZ, edgeZ, skillZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "./schema.js";
|
|
34
34
|
export type { WorkflowError } from "./schema.js";
|
|
35
|
-
export { buildAutoMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
-
export type { ProviderContextOptions } from "./mcp.js";
|
|
35
|
+
export { buildAutoMcpServers, buildSkillMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
+
export type { ProviderContextOptions, SkillMcpOptions } from "./mcp.js";
|
|
37
37
|
export type { McpServerConfig, McpAutoConfig } from "./types.js";
|
|
38
38
|
export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
|
|
39
39
|
export type { BuildWorkflowOptions } from "./workflow-builder.js";
|
package/dist/index.js
CHANGED
|
@@ -29,11 +29,11 @@ export { ClaudeClient } from "./claude.js";
|
|
|
29
29
|
// Skills
|
|
30
30
|
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
// Node-only skill discovery (filesystem-based)
|
|
32
|
-
export { loadCustomSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
32
|
+
export { loadCustomSkills, discoverSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
33
|
// Schema & validation
|
|
34
|
-
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
34
|
+
export { workflowZ, nodeZ, edgeZ, skillZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "./schema.js";
|
|
35
35
|
// MCP auto-injection
|
|
36
|
-
export { buildAutoMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
+
export { buildAutoMcpServers, buildSkillMcpServers, buildProviderContext } from "./mcp.js";
|
|
37
37
|
// Workflow builder
|
|
38
38
|
export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
|
|
39
39
|
// Templates
|
package/dist/mcp.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface SkillMcpOptions {
|
|
|
6
6
|
credentials: Record<string, string>;
|
|
7
7
|
/** User-supplied MCP servers — always win on key conflict. */
|
|
8
8
|
userMcpServers?: Record<string, McpServerConfig>;
|
|
9
|
+
/** MCP servers declared by custom skills. Only wired when the skill is referenced. */
|
|
10
|
+
skillMcpServers?: Record<string, McpServerConfig>;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Build MCP server configs for the skills a workflow references.
|
package/dist/mcp.js
CHANGED
|
@@ -155,6 +155,14 @@ export function buildSkillMcpServers(opts) {
|
|
|
155
155
|
env: { ASANA_ACCESS_TOKEN: creds.ASANA_ACCESS_TOKEN },
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
+
// Skill-declared MCP servers — only include for referenced skills.
|
|
159
|
+
if (opts.skillMcpServers) {
|
|
160
|
+
for (const [skillId, mcpConfig] of Object.entries(opts.skillMcpServers)) {
|
|
161
|
+
if (refs.has(skillId) && !auto[skillId]) {
|
|
162
|
+
auto[skillId] = mcpConfig;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
158
166
|
// User-supplied servers always win on key conflict.
|
|
159
167
|
return { ...auto, ...(opts.userMcpServers ?? {}) };
|
|
160
168
|
}
|