@sweny-ai/core 0.1.88 → 0.1.90
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/cli/publish.js +3 -3
- package/dist/cli/skill.d.ts +22 -2
- package/dist/cli/skill.js +28 -24
- package/dist/schema.d.ts +163 -6
- package/dist/schema.js +151 -9
- package/dist/skills/custom-loader.js +10 -7
- package/dist/types.d.ts +60 -4
- package/dist/types.js +51 -0
- package/package.json +2 -2
package/dist/cli/publish.js
CHANGED
|
@@ -15,9 +15,9 @@ import { parse as parseYaml } from "yaml";
|
|
|
15
15
|
import { parseWorkflow, validateWorkflow } from "../schema.js";
|
|
16
16
|
import { builtinSkills } from "../skills/index.js";
|
|
17
17
|
import { discoverSkillsWithDiagnostics } from "../skills/custom-loader.js";
|
|
18
|
+
import { isValidSkillId } from "../types.js";
|
|
18
19
|
// ── Types ──────────────────────────────────────────────────────────────
|
|
19
20
|
const VALID_CATEGORIES = ["triage", "security", "devops", "code-review", "testing", "content", "ops"];
|
|
20
|
-
const VALID_SKILL_ID = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
21
21
|
// ── Validation helpers ─────────────────────────────────────────────────
|
|
22
22
|
export function validateWorkflowFile(filePath) {
|
|
23
23
|
const errors = [];
|
|
@@ -119,8 +119,8 @@ export function validateSkillDir(dirPath) {
|
|
|
119
119
|
if (!id) {
|
|
120
120
|
errors.push("Missing 'name' field in frontmatter");
|
|
121
121
|
}
|
|
122
|
-
else if (!
|
|
123
|
-
errors.push(`Invalid skill ID: "${id}"
|
|
122
|
+
else if (!isValidSkillId(id)) {
|
|
123
|
+
errors.push(`Invalid skill ID: "${id}" (must be lowercase alphanumeric + hyphens, no consecutive hyphens, max 64 chars)`);
|
|
124
124
|
}
|
|
125
125
|
// Check directory name matches skill name
|
|
126
126
|
const dirName = path.basename(dirPath);
|
package/dist/cli/skill.d.ts
CHANGED
|
@@ -14,14 +14,34 @@
|
|
|
14
14
|
* and `../skills/index.js`; this CLI is a thin authoring shell over them.
|
|
15
15
|
*/
|
|
16
16
|
import { Command } from "commander";
|
|
17
|
-
|
|
18
|
-
type SkillCategory = (typeof VALID_CATEGORIES)[number];
|
|
17
|
+
import { type SkillCategory, type SkillHarnessKey } from "../types.js";
|
|
19
18
|
/** Build the SKILL.md body for a fresh scaffold. Exported for tests. */
|
|
20
19
|
export declare function renderSkillTemplate(opts: {
|
|
21
20
|
id: string;
|
|
22
21
|
description: string;
|
|
23
22
|
category: SkillCategory;
|
|
24
23
|
}): string;
|
|
24
|
+
interface NewOptions {
|
|
25
|
+
description?: string;
|
|
26
|
+
category?: string;
|
|
27
|
+
harness?: SkillHarnessKey;
|
|
28
|
+
force?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Action handler for `sweny skill new`. Exported for unit tests; production
|
|
32
|
+
* code goes through `registerSkillCommand`. `cwd` defaults to `process.cwd()`
|
|
33
|
+
* so the CLI works as expected; tests pass a tmp dir for isolation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runSkillNew(idArg: string, options: NewOptions, cwd?: string): void;
|
|
36
|
+
interface ListOptions {
|
|
37
|
+
json?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Action handler for `sweny skill list`. Exported for unit tests; production
|
|
41
|
+
* code goes through `registerSkillCommand`. `cwd` and `env` injection lets
|
|
42
|
+
* tests exercise the configured-badge logic without polluting global state.
|
|
43
|
+
*/
|
|
44
|
+
export declare function runSkillList(options: ListOptions, cwd?: string, env?: Record<string, string | undefined>): void;
|
|
25
45
|
/** Register the `sweny skill` subcommand group on the parent program. */
|
|
26
46
|
export declare function registerSkillCommand(program: Command): void;
|
|
27
47
|
export {};
|
package/dist/cli/skill.js
CHANGED
|
@@ -18,15 +18,11 @@ import * as path from "node:path";
|
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
import { builtinSkills } from "../skills/index.js";
|
|
20
20
|
import { configuredSkillsWithDiagnostics, discoverSkillsWithDiagnostics } from "../skills/custom-loader.js";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const HARNESS_DIRS =
|
|
25
|
-
|
|
26
|
-
sweny: ".sweny/skills",
|
|
27
|
-
agents: ".agents/skills",
|
|
28
|
-
gemini: ".gemini/skills",
|
|
29
|
-
};
|
|
21
|
+
import { SKILL_CATEGORIES, SKILL_HARNESSES, isValidSkillId, } from "../types.js";
|
|
22
|
+
// Lookup table: harness key -> directory. Built once from SKILL_HARNESSES so
|
|
23
|
+
// the CLI cannot drift from the loader's scan list.
|
|
24
|
+
const HARNESS_DIRS = Object.fromEntries(SKILL_HARNESSES.map((h) => [h.key, h.path]));
|
|
25
|
+
const HARNESS_KEYS = SKILL_HARNESSES.map((h) => h.key);
|
|
30
26
|
/** Build the SKILL.md body for a fresh scaffold. Exported for tests. */
|
|
31
27
|
export function renderSkillTemplate(opts) {
|
|
32
28
|
const { id, description, category } = opts;
|
|
@@ -80,28 +76,32 @@ export function renderSkillTemplate(opts) {
|
|
|
80
76
|
lines.push("");
|
|
81
77
|
return lines.join("\n");
|
|
82
78
|
}
|
|
83
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Action handler for `sweny skill new`. Exported for unit tests; production
|
|
81
|
+
* code goes through `registerSkillCommand`. `cwd` defaults to `process.cwd()`
|
|
82
|
+
* so the CLI works as expected; tests pass a tmp dir for isolation.
|
|
83
|
+
*/
|
|
84
|
+
export function runSkillNew(idArg, options, cwd = process.cwd()) {
|
|
84
85
|
const id = idArg.toLowerCase();
|
|
85
|
-
if (!
|
|
86
|
+
if (!isValidSkillId(id)) {
|
|
86
87
|
console.error(chalk.red(` Invalid skill id "${idArg}".\n Skill ids must be lowercase alphanumeric + hyphens (no consecutive hyphens), max 64 chars.`));
|
|
87
88
|
process.exit(2);
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
const description = (options.description ?? `Custom ${id} skill`).trim();
|
|
91
92
|
const category = (options.category ?? "general");
|
|
92
|
-
if (!
|
|
93
|
-
console.error(chalk.red(` Invalid category "${category}".\n Allowed: ${
|
|
93
|
+
if (!SKILL_CATEGORIES.includes(category)) {
|
|
94
|
+
console.error(chalk.red(` Invalid category "${category}".\n Allowed: ${SKILL_CATEGORIES.join(", ")}`));
|
|
94
95
|
process.exit(2);
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
const harness = options.harness ?? "claude";
|
|
98
99
|
const baseDir = HARNESS_DIRS[harness];
|
|
99
100
|
if (!baseDir) {
|
|
100
|
-
console.error(chalk.red(` Unknown harness "${harness}". Allowed: ${
|
|
101
|
+
console.error(chalk.red(` Unknown harness "${harness}". Allowed: ${HARNESS_KEYS.join(", ")}`));
|
|
101
102
|
process.exit(2);
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
|
-
const cwd = process.cwd();
|
|
105
105
|
const skillDir = path.join(cwd, baseDir, id);
|
|
106
106
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
107
107
|
if (fs.existsSync(skillFile) && !options.force) {
|
|
@@ -121,16 +121,20 @@ function runSkillNew(idArg, options) {
|
|
|
121
121
|
console.log(chalk.dim(` 5. Run \`sweny skill list\` to confirm it discovers correctly.`));
|
|
122
122
|
console.log();
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Action handler for `sweny skill list`. Exported for unit tests; production
|
|
126
|
+
* code goes through `registerSkillCommand`. `cwd` and `env` injection lets
|
|
127
|
+
* tests exercise the configured-badge logic without polluting global state.
|
|
128
|
+
*/
|
|
129
|
+
export function runSkillList(options, cwd = process.cwd(), env = process.env) {
|
|
130
|
+
// We want the FULL list (built-in + custom) even when env vars aren't set.
|
|
131
|
+
// configuredSkills filters by env, which hides authoring-time skills the
|
|
132
|
+
// user just scaffolded, so combine sources directly.
|
|
129
133
|
const { skills: customSkills, warnings: customWarnings } = discoverSkillsWithDiagnostics(cwd);
|
|
130
134
|
const customIds = new Set(customSkills.map((s) => s.id));
|
|
131
135
|
const builtinList = builtinSkills.filter((s) => !customIds.has(s.id));
|
|
132
|
-
// Configured map drives the "configured?" badge
|
|
133
|
-
const configuredIds = new Set(configuredSkillsWithDiagnostics(
|
|
136
|
+
// Configured map drives the "configured?" badge: required env vars present.
|
|
137
|
+
const configuredIds = new Set(configuredSkillsWithDiagnostics(env, cwd).skills.map((s) => s.id));
|
|
134
138
|
if (options.json) {
|
|
135
139
|
const data = [
|
|
136
140
|
...builtinList.map((s) => ({
|
|
@@ -188,8 +192,8 @@ export function registerSkillCommand(program) {
|
|
|
188
192
|
.command("new <id>")
|
|
189
193
|
.description("Scaffold a new SKILL.md in .claude/skills/<id>/")
|
|
190
194
|
.option("-d, --description <text>", "One-line description (required field in frontmatter)")
|
|
191
|
-
.option("-c, --category <category>", `Skill category (${
|
|
192
|
-
.option("--harness <harness>", `Where to place the skill (${
|
|
195
|
+
.option("-c, --category <category>", `Skill category (${SKILL_CATEGORIES.join("|")})`, "general")
|
|
196
|
+
.option("--harness <harness>", `Where to place the skill (${HARNESS_KEYS.join("|")})`, "claude")
|
|
193
197
|
.option("--force", "Overwrite an existing SKILL.md")
|
|
194
198
|
.action((id, options) => runSkillNew(id, options));
|
|
195
199
|
skillCmd
|
package/dist/schema.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare const toolZ: z.ZodObject<{
|
|
|
42
42
|
description: string;
|
|
43
43
|
input_schema: Record<string, unknown>;
|
|
44
44
|
}>;
|
|
45
|
-
export declare const skillCategoryZ: z.ZodEnum<["
|
|
45
|
+
export declare const skillCategoryZ: z.ZodEnum<["general", "git", "tasks", "notification", "observability", "data"]>;
|
|
46
46
|
export declare const mcpServerConfigZ: z.ZodEffects<z.ZodObject<{
|
|
47
47
|
type: z.ZodOptional<z.ZodEnum<["stdio", "http"]>>;
|
|
48
48
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -172,7 +172,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
172
172
|
id: z.ZodString;
|
|
173
173
|
name: z.ZodString;
|
|
174
174
|
description: z.ZodString;
|
|
175
|
-
category: z.ZodEnum<["
|
|
175
|
+
category: z.ZodEnum<["general", "git", "tasks", "notification", "observability", "data"]>;
|
|
176
176
|
config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
177
177
|
description: z.ZodString;
|
|
178
178
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -241,7 +241,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
241
241
|
id: string;
|
|
242
242
|
name: string;
|
|
243
243
|
description: string;
|
|
244
|
-
category: "
|
|
244
|
+
category: "general" | "git" | "tasks" | "notification" | "observability" | "data";
|
|
245
245
|
config: Record<string, {
|
|
246
246
|
description: string;
|
|
247
247
|
required?: boolean | undefined;
|
|
@@ -266,7 +266,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
266
266
|
id: string;
|
|
267
267
|
name: string;
|
|
268
268
|
description: string;
|
|
269
|
-
category: "
|
|
269
|
+
category: "general" | "git" | "tasks" | "notification" | "observability" | "data";
|
|
270
270
|
instruction?: string | undefined;
|
|
271
271
|
mcp?: {
|
|
272
272
|
url?: string | undefined;
|
|
@@ -291,7 +291,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
291
291
|
id: string;
|
|
292
292
|
name: string;
|
|
293
293
|
description: string;
|
|
294
|
-
category: "
|
|
294
|
+
category: "general" | "git" | "tasks" | "notification" | "observability" | "data";
|
|
295
295
|
config: Record<string, {
|
|
296
296
|
description: string;
|
|
297
297
|
required?: boolean | undefined;
|
|
@@ -316,7 +316,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
316
316
|
id: string;
|
|
317
317
|
name: string;
|
|
318
318
|
description: string;
|
|
319
|
-
category: "
|
|
319
|
+
category: "general" | "git" | "tasks" | "notification" | "observability" | "data";
|
|
320
320
|
instruction?: string | undefined;
|
|
321
321
|
mcp?: {
|
|
322
322
|
url?: string | undefined;
|
|
@@ -3002,3 +3002,160 @@ export declare const workflowJsonSchema: {
|
|
|
3002
3002
|
};
|
|
3003
3003
|
};
|
|
3004
3004
|
};
|
|
3005
|
+
/**
|
|
3006
|
+
* Skill JSON Schema, generated from runtime constants.
|
|
3007
|
+
*
|
|
3008
|
+
* Companion to {@link workflowJsonSchema}: published at
|
|
3009
|
+
* https://spec.sweny.ai/schemas/skill.json by the
|
|
3010
|
+
* `write-public-schema.mjs` build step. Validates the structural shape
|
|
3011
|
+
* of a Skill (id, name, description, category, config, plus optional
|
|
3012
|
+
* tools/instruction/mcp).
|
|
3013
|
+
*
|
|
3014
|
+
* Every enum and the `id` pattern + maxLength are imported from
|
|
3015
|
+
* `types.ts` so adding a category, harness, transport, or changing the
|
|
3016
|
+
* id rule updates the published schema with no manual sync.
|
|
3017
|
+
*/
|
|
3018
|
+
export declare const skillJsonSchema: {
|
|
3019
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
3020
|
+
readonly $id: "https://spec.sweny.ai/schemas/skill.json";
|
|
3021
|
+
readonly title: "SWEny Skill";
|
|
3022
|
+
readonly description: "A composable tool bundle that provides capabilities to workflow nodes.";
|
|
3023
|
+
readonly type: "object";
|
|
3024
|
+
readonly required: readonly ["id", "name", "description", "category", "config"];
|
|
3025
|
+
readonly anyOf: readonly [{
|
|
3026
|
+
readonly required: readonly ["tools"];
|
|
3027
|
+
}, {
|
|
3028
|
+
readonly required: readonly ["instruction"];
|
|
3029
|
+
}, {
|
|
3030
|
+
readonly required: readonly ["mcp"];
|
|
3031
|
+
}];
|
|
3032
|
+
readonly additionalProperties: false;
|
|
3033
|
+
readonly properties: {
|
|
3034
|
+
readonly id: {
|
|
3035
|
+
readonly type: "string";
|
|
3036
|
+
readonly minLength: 1;
|
|
3037
|
+
readonly maxLength: 64;
|
|
3038
|
+
readonly pattern: string;
|
|
3039
|
+
readonly description: "Unique skill identifier. Referenced by nodes' skills arrays. Lowercase kebab-case recommended.";
|
|
3040
|
+
};
|
|
3041
|
+
readonly name: {
|
|
3042
|
+
readonly type: "string";
|
|
3043
|
+
readonly minLength: 1;
|
|
3044
|
+
readonly description: "Human-readable skill name.";
|
|
3045
|
+
};
|
|
3046
|
+
readonly description: {
|
|
3047
|
+
readonly type: "string";
|
|
3048
|
+
readonly description: "What this skill provides.";
|
|
3049
|
+
};
|
|
3050
|
+
readonly category: {
|
|
3051
|
+
readonly type: "string";
|
|
3052
|
+
readonly enum: readonly ["general", "git", "tasks", "notification", "observability", "data"];
|
|
3053
|
+
readonly description: "Functional category.";
|
|
3054
|
+
};
|
|
3055
|
+
readonly config: {
|
|
3056
|
+
readonly type: "object";
|
|
3057
|
+
readonly description: "Configuration fields required by this skill.";
|
|
3058
|
+
readonly additionalProperties: {
|
|
3059
|
+
readonly $ref: "#/$defs/ConfigField";
|
|
3060
|
+
};
|
|
3061
|
+
};
|
|
3062
|
+
readonly tools: {
|
|
3063
|
+
readonly type: "array";
|
|
3064
|
+
readonly description: "Tools this skill provides to nodes.";
|
|
3065
|
+
readonly items: {
|
|
3066
|
+
readonly $ref: "#/$defs/Tool";
|
|
3067
|
+
};
|
|
3068
|
+
};
|
|
3069
|
+
readonly instruction: {
|
|
3070
|
+
readonly type: "string";
|
|
3071
|
+
readonly description: "Natural language expertise injected into the node prompt when this skill is referenced.";
|
|
3072
|
+
};
|
|
3073
|
+
readonly mcp: {
|
|
3074
|
+
readonly $ref: "#/$defs/McpServerConfig";
|
|
3075
|
+
readonly description: "External MCP server definition wired for nodes referencing this skill.";
|
|
3076
|
+
};
|
|
3077
|
+
};
|
|
3078
|
+
readonly $defs: {
|
|
3079
|
+
readonly ConfigField: {
|
|
3080
|
+
readonly type: "object";
|
|
3081
|
+
readonly required: readonly ["description"];
|
|
3082
|
+
readonly additionalProperties: false;
|
|
3083
|
+
readonly properties: {
|
|
3084
|
+
readonly description: {
|
|
3085
|
+
readonly type: "string";
|
|
3086
|
+
readonly description: "Human-readable description of this config field.";
|
|
3087
|
+
};
|
|
3088
|
+
readonly required: {
|
|
3089
|
+
readonly type: "boolean";
|
|
3090
|
+
readonly default: false;
|
|
3091
|
+
readonly description: "Whether this field must be provided for the skill to function.";
|
|
3092
|
+
};
|
|
3093
|
+
readonly env: {
|
|
3094
|
+
readonly type: "string";
|
|
3095
|
+
readonly description: "Default environment variable to read this value from.";
|
|
3096
|
+
};
|
|
3097
|
+
};
|
|
3098
|
+
};
|
|
3099
|
+
readonly Tool: {
|
|
3100
|
+
readonly type: "object";
|
|
3101
|
+
readonly required: readonly ["name", "description", "input_schema"];
|
|
3102
|
+
readonly additionalProperties: false;
|
|
3103
|
+
readonly properties: {
|
|
3104
|
+
readonly name: {
|
|
3105
|
+
readonly type: "string";
|
|
3106
|
+
readonly description: "Tool name. Must be unique within the skill.";
|
|
3107
|
+
};
|
|
3108
|
+
readonly description: {
|
|
3109
|
+
readonly type: "string";
|
|
3110
|
+
readonly description: "What this tool does. Provided to the AI model for tool selection.";
|
|
3111
|
+
};
|
|
3112
|
+
readonly input_schema: {
|
|
3113
|
+
readonly type: "object";
|
|
3114
|
+
readonly description: "JSON Schema defining the tool's input parameters.";
|
|
3115
|
+
};
|
|
3116
|
+
};
|
|
3117
|
+
};
|
|
3118
|
+
readonly McpServerConfig: {
|
|
3119
|
+
readonly type: "object";
|
|
3120
|
+
readonly description: "External MCP server definition.";
|
|
3121
|
+
readonly additionalProperties: false;
|
|
3122
|
+
readonly properties: {
|
|
3123
|
+
readonly type: {
|
|
3124
|
+
readonly type: "string";
|
|
3125
|
+
readonly enum: readonly ["stdio", "http"];
|
|
3126
|
+
readonly description: "Transport type. Inferred from presence of command (stdio) or url (http) when omitted.";
|
|
3127
|
+
};
|
|
3128
|
+
readonly command: {
|
|
3129
|
+
readonly type: "string";
|
|
3130
|
+
readonly description: "Spawn command (stdio transport).";
|
|
3131
|
+
};
|
|
3132
|
+
readonly args: {
|
|
3133
|
+
readonly type: "array";
|
|
3134
|
+
readonly items: {
|
|
3135
|
+
readonly type: "string";
|
|
3136
|
+
};
|
|
3137
|
+
readonly description: "Arguments for the command.";
|
|
3138
|
+
};
|
|
3139
|
+
readonly url: {
|
|
3140
|
+
readonly type: "string";
|
|
3141
|
+
readonly format: "uri";
|
|
3142
|
+
readonly description: "HTTP endpoint (HTTP transport).";
|
|
3143
|
+
};
|
|
3144
|
+
readonly headers: {
|
|
3145
|
+
readonly type: "object";
|
|
3146
|
+
readonly additionalProperties: {
|
|
3147
|
+
readonly type: "string";
|
|
3148
|
+
};
|
|
3149
|
+
readonly description: "HTTP headers (HTTP transport only).";
|
|
3150
|
+
};
|
|
3151
|
+
readonly env: {
|
|
3152
|
+
readonly type: "object";
|
|
3153
|
+
readonly additionalProperties: {
|
|
3154
|
+
readonly type: "string";
|
|
3155
|
+
};
|
|
3156
|
+
readonly description: "Environment variable names the server needs. Values are descriptions, not secrets.";
|
|
3157
|
+
};
|
|
3158
|
+
};
|
|
3159
|
+
};
|
|
3160
|
+
};
|
|
3161
|
+
};
|
package/dist/schema.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* parsing untrusted input (JSON/YAML import, Studio editor).
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import { EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, REQUIRES_ON_FAIL, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, } from "./types.js";
|
|
10
11
|
import { sourceZ } from "./sources.js";
|
|
11
12
|
export { sourceZ };
|
|
12
13
|
// ─── Zod Schemas ─────────────────────────────────────────────────
|
|
@@ -26,10 +27,10 @@ export const toolZ = z.object({
|
|
|
26
27
|
description: z.string(),
|
|
27
28
|
input_schema: jsonSchemaZ,
|
|
28
29
|
});
|
|
29
|
-
export const skillCategoryZ = z.enum(
|
|
30
|
+
export const skillCategoryZ = z.enum(SKILL_CATEGORIES);
|
|
30
31
|
export const mcpServerConfigZ = z
|
|
31
32
|
.object({
|
|
32
|
-
type: z.enum(
|
|
33
|
+
type: z.enum(MCP_TRANSPORTS).optional(),
|
|
33
34
|
command: z.string().optional(),
|
|
34
35
|
args: z.array(z.string()).optional(),
|
|
35
36
|
url: z.string().optional(),
|
|
@@ -90,8 +91,8 @@ export const outputMatchZ = z
|
|
|
90
91
|
const operators = [m.equals !== undefined, m.in !== undefined, m.matches !== undefined];
|
|
91
92
|
return operators.filter(Boolean).length === 1;
|
|
92
93
|
}, { message: "output_matches entry must declare exactly one of: equals, in, matches" });
|
|
93
|
-
export const evaluatorKindZ = z.enum(
|
|
94
|
-
export const evalPolicyZ = z.enum(
|
|
94
|
+
export const evaluatorKindZ = z.enum(EVALUATOR_KINDS);
|
|
95
|
+
export const evalPolicyZ = z.enum(EVAL_POLICIES);
|
|
95
96
|
export const evaluatorRuleZ = z
|
|
96
97
|
.object({
|
|
97
98
|
any_tool_called: z.array(z.string().min(1)).min(1).optional(),
|
|
@@ -170,7 +171,7 @@ export const nodeRequiresZ = z
|
|
|
170
171
|
.object({
|
|
171
172
|
output_required: z.array(z.string().min(1)).min(1).optional(),
|
|
172
173
|
output_matches: z.array(outputMatchZ).min(1).optional(),
|
|
173
|
-
on_fail: z.enum(
|
|
174
|
+
on_fail: z.enum(REQUIRES_ON_FAIL).optional(),
|
|
174
175
|
})
|
|
175
176
|
.strict()
|
|
176
177
|
.refine((r) => r.output_required !== undefined || r.output_matches !== undefined, {
|
|
@@ -482,7 +483,7 @@ export const workflowJsonSchema = {
|
|
|
482
483
|
minLength: 1,
|
|
483
484
|
description: "Stable identifier for this evaluator. Used in EvalResult and retry preambles.",
|
|
484
485
|
},
|
|
485
|
-
kind: { type: "string", enum: [
|
|
486
|
+
kind: { type: "string", enum: [...EVALUATOR_KINDS] },
|
|
486
487
|
rule: {
|
|
487
488
|
type: "object",
|
|
488
489
|
description: "Required for value and function kinds. Shape depends on kind.",
|
|
@@ -591,7 +592,7 @@ export const workflowJsonSchema = {
|
|
|
591
592
|
},
|
|
592
593
|
eval_policy: {
|
|
593
594
|
type: "string",
|
|
594
|
-
enum: [
|
|
595
|
+
enum: [...EVAL_POLICIES],
|
|
595
596
|
default: "all_pass",
|
|
596
597
|
description: "How evaluator results aggregate. v1 implements all_pass; the others are reserved.",
|
|
597
598
|
},
|
|
@@ -618,7 +619,7 @@ export const workflowJsonSchema = {
|
|
|
618
619
|
items: { $ref: "#/$defs/OutputMatch" },
|
|
619
620
|
minItems: 1,
|
|
620
621
|
},
|
|
621
|
-
on_fail: { type: "string", enum: [
|
|
622
|
+
on_fail: { type: "string", enum: [...REQUIRES_ON_FAIL] },
|
|
622
623
|
},
|
|
623
624
|
},
|
|
624
625
|
retry: {
|
|
@@ -689,7 +690,7 @@ export const workflowJsonSchema = {
|
|
|
689
690
|
// Round 2: reject unknown keys to match Zod mcpServerConfigZ.strict().
|
|
690
691
|
additionalProperties: false,
|
|
691
692
|
properties: {
|
|
692
|
-
type: { type: "string", enum: [
|
|
693
|
+
type: { type: "string", enum: [...MCP_TRANSPORTS] },
|
|
693
694
|
command: { type: "string" },
|
|
694
695
|
args: { type: "array", items: { type: "string" } },
|
|
695
696
|
url: { type: "string" },
|
|
@@ -702,3 +703,144 @@ export const workflowJsonSchema = {
|
|
|
702
703
|
},
|
|
703
704
|
},
|
|
704
705
|
};
|
|
706
|
+
/**
|
|
707
|
+
* Skill JSON Schema, generated from runtime constants.
|
|
708
|
+
*
|
|
709
|
+
* Companion to {@link workflowJsonSchema}: published at
|
|
710
|
+
* https://spec.sweny.ai/schemas/skill.json by the
|
|
711
|
+
* `write-public-schema.mjs` build step. Validates the structural shape
|
|
712
|
+
* of a Skill (id, name, description, category, config, plus optional
|
|
713
|
+
* tools/instruction/mcp).
|
|
714
|
+
*
|
|
715
|
+
* Every enum and the `id` pattern + maxLength are imported from
|
|
716
|
+
* `types.ts` so adding a category, harness, transport, or changing the
|
|
717
|
+
* id rule updates the published schema with no manual sync.
|
|
718
|
+
*/
|
|
719
|
+
export const skillJsonSchema = {
|
|
720
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
721
|
+
$id: "https://spec.sweny.ai/schemas/skill.json",
|
|
722
|
+
title: "SWEny Skill",
|
|
723
|
+
description: "A composable tool bundle that provides capabilities to workflow nodes.",
|
|
724
|
+
type: "object",
|
|
725
|
+
required: ["id", "name", "description", "category", "config"],
|
|
726
|
+
anyOf: [{ required: ["tools"] }, { required: ["instruction"] }, { required: ["mcp"] }],
|
|
727
|
+
additionalProperties: false,
|
|
728
|
+
properties: {
|
|
729
|
+
id: {
|
|
730
|
+
type: "string",
|
|
731
|
+
minLength: 1,
|
|
732
|
+
maxLength: SKILL_ID_MAX_LENGTH,
|
|
733
|
+
pattern: SKILL_ID_PATTERN.source,
|
|
734
|
+
description: "Unique skill identifier. Referenced by nodes' skills arrays. Lowercase kebab-case recommended.",
|
|
735
|
+
},
|
|
736
|
+
name: {
|
|
737
|
+
type: "string",
|
|
738
|
+
minLength: 1,
|
|
739
|
+
description: "Human-readable skill name.",
|
|
740
|
+
},
|
|
741
|
+
description: {
|
|
742
|
+
type: "string",
|
|
743
|
+
description: "What this skill provides.",
|
|
744
|
+
},
|
|
745
|
+
category: {
|
|
746
|
+
type: "string",
|
|
747
|
+
enum: [...SKILL_CATEGORIES],
|
|
748
|
+
description: "Functional category.",
|
|
749
|
+
},
|
|
750
|
+
config: {
|
|
751
|
+
type: "object",
|
|
752
|
+
description: "Configuration fields required by this skill.",
|
|
753
|
+
additionalProperties: { $ref: "#/$defs/ConfigField" },
|
|
754
|
+
},
|
|
755
|
+
tools: {
|
|
756
|
+
type: "array",
|
|
757
|
+
description: "Tools this skill provides to nodes.",
|
|
758
|
+
items: { $ref: "#/$defs/Tool" },
|
|
759
|
+
},
|
|
760
|
+
instruction: {
|
|
761
|
+
type: "string",
|
|
762
|
+
description: "Natural language expertise injected into the node prompt when this skill is referenced.",
|
|
763
|
+
},
|
|
764
|
+
mcp: {
|
|
765
|
+
$ref: "#/$defs/McpServerConfig",
|
|
766
|
+
description: "External MCP server definition wired for nodes referencing this skill.",
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
$defs: {
|
|
770
|
+
ConfigField: {
|
|
771
|
+
type: "object",
|
|
772
|
+
required: ["description"],
|
|
773
|
+
additionalProperties: false,
|
|
774
|
+
properties: {
|
|
775
|
+
description: {
|
|
776
|
+
type: "string",
|
|
777
|
+
description: "Human-readable description of this config field.",
|
|
778
|
+
},
|
|
779
|
+
required: {
|
|
780
|
+
type: "boolean",
|
|
781
|
+
default: false,
|
|
782
|
+
description: "Whether this field must be provided for the skill to function.",
|
|
783
|
+
},
|
|
784
|
+
env: {
|
|
785
|
+
type: "string",
|
|
786
|
+
description: "Default environment variable to read this value from.",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
Tool: {
|
|
791
|
+
type: "object",
|
|
792
|
+
required: ["name", "description", "input_schema"],
|
|
793
|
+
additionalProperties: false,
|
|
794
|
+
properties: {
|
|
795
|
+
name: {
|
|
796
|
+
type: "string",
|
|
797
|
+
description: "Tool name. Must be unique within the skill.",
|
|
798
|
+
},
|
|
799
|
+
description: {
|
|
800
|
+
type: "string",
|
|
801
|
+
description: "What this tool does. Provided to the AI model for tool selection.",
|
|
802
|
+
},
|
|
803
|
+
input_schema: {
|
|
804
|
+
type: "object",
|
|
805
|
+
description: "JSON Schema defining the tool's input parameters.",
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
McpServerConfig: {
|
|
810
|
+
type: "object",
|
|
811
|
+
description: "External MCP server definition.",
|
|
812
|
+
additionalProperties: false,
|
|
813
|
+
properties: {
|
|
814
|
+
type: {
|
|
815
|
+
type: "string",
|
|
816
|
+
enum: [...MCP_TRANSPORTS],
|
|
817
|
+
description: "Transport type. Inferred from presence of command (stdio) or url (http) when omitted.",
|
|
818
|
+
},
|
|
819
|
+
command: {
|
|
820
|
+
type: "string",
|
|
821
|
+
description: "Spawn command (stdio transport).",
|
|
822
|
+
},
|
|
823
|
+
args: {
|
|
824
|
+
type: "array",
|
|
825
|
+
items: { type: "string" },
|
|
826
|
+
description: "Arguments for the command.",
|
|
827
|
+
},
|
|
828
|
+
url: {
|
|
829
|
+
type: "string",
|
|
830
|
+
format: "uri",
|
|
831
|
+
description: "HTTP endpoint (HTTP transport).",
|
|
832
|
+
},
|
|
833
|
+
headers: {
|
|
834
|
+
type: "object",
|
|
835
|
+
additionalProperties: { type: "string" },
|
|
836
|
+
description: "HTTP headers (HTTP transport only).",
|
|
837
|
+
},
|
|
838
|
+
env: {
|
|
839
|
+
type: "object",
|
|
840
|
+
additionalProperties: { type: "string" },
|
|
841
|
+
description: "Environment variable names the server needs. Values are descriptions, not secrets.",
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
};
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import YAML from "yaml";
|
|
14
|
+
import { SKILL_CATEGORIES, SKILL_HARNESSES, isValidSkillId } from "../types.js";
|
|
14
15
|
import { builtinSkills, isSkillConfigured } from "./index.js";
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Directories to scan, in ascending priority order. Last match wins.
|
|
18
|
+
*
|
|
19
|
+
* Derived from {@link SKILL_HARNESSES} so the loader and the CLI authoring
|
|
20
|
+
* side cannot disagree on what counts as a skill harness directory.
|
|
21
|
+
*/
|
|
22
|
+
const SKILL_DIRS = SKILL_HARNESSES.map((h) => h.path);
|
|
19
23
|
/**
|
|
20
24
|
* Discover custom skills and surface diagnostics for broken ones.
|
|
21
25
|
*
|
|
@@ -99,12 +103,11 @@ function parseSkillMd(content, path) {
|
|
|
99
103
|
}
|
|
100
104
|
// Narrow the loosely-typed YAML output into the fields we expect
|
|
101
105
|
const fm = raw;
|
|
102
|
-
const
|
|
103
|
-
const category = typeof fm.category === "string" && VALID_CATEGORIES.includes(fm.category)
|
|
106
|
+
const category = typeof fm.category === "string" && SKILL_CATEGORIES.includes(fm.category)
|
|
104
107
|
? fm.category
|
|
105
108
|
: "general";
|
|
106
109
|
const id = String(fm.name);
|
|
107
|
-
if (!
|
|
110
|
+
if (!isValidSkillId(id)) {
|
|
108
111
|
return {
|
|
109
112
|
kind: "err",
|
|
110
113
|
diagnostic: {
|
package/dist/types.d.ts
CHANGED
|
@@ -22,8 +22,56 @@ export interface ConfigField {
|
|
|
22
22
|
/** Default environment variable to read from */
|
|
23
23
|
env?: string;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Skill categories: used for per-node validation and grouping.
|
|
27
|
+
*
|
|
28
|
+
* Single source of truth for runtime + compile-time. The `as const` tuple
|
|
29
|
+
* lets the CLI iterate it for help text and the loader use it for runtime
|
|
30
|
+
* validation; `SkillCategory` is derived so adding a category requires
|
|
31
|
+
* only this line.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SKILL_CATEGORIES: readonly ["general", "git", "tasks", "notification", "observability", "data"];
|
|
34
|
+
export type SkillCategory = (typeof SKILL_CATEGORIES)[number];
|
|
35
|
+
/**
|
|
36
|
+
* Skill harness directories, listed in ascending priority order.
|
|
37
|
+
*
|
|
38
|
+
* Both the loader (which scans these directories last-wins for ID
|
|
39
|
+
* collisions) and the CLI (which writes scaffolds into one of them)
|
|
40
|
+
* must agree on this list. A divergence means scaffolded skills land
|
|
41
|
+
* somewhere the loader doesn't read, or the loader reads from a
|
|
42
|
+
* directory the CLI can't target.
|
|
43
|
+
*
|
|
44
|
+
* Order matters: the loader treats later entries as higher priority,
|
|
45
|
+
* so `.sweny/skills/` overrides `.claude/skills/` on a name collision.
|
|
46
|
+
*/
|
|
47
|
+
export declare const SKILL_HARNESSES: readonly [{
|
|
48
|
+
readonly key: "gemini";
|
|
49
|
+
readonly path: ".gemini/skills";
|
|
50
|
+
}, {
|
|
51
|
+
readonly key: "agents";
|
|
52
|
+
readonly path: ".agents/skills";
|
|
53
|
+
}, {
|
|
54
|
+
readonly key: "claude";
|
|
55
|
+
readonly path: ".claude/skills";
|
|
56
|
+
}, {
|
|
57
|
+
readonly key: "sweny";
|
|
58
|
+
readonly path: ".sweny/skills";
|
|
59
|
+
}];
|
|
60
|
+
export type SkillHarnessKey = (typeof SKILL_HARNESSES)[number]["key"];
|
|
61
|
+
/**
|
|
62
|
+
* Skill ID validation rules, declared once for runtime + spec.
|
|
63
|
+
*
|
|
64
|
+
* The pattern excludes consecutive hyphens via a negative lookahead so a
|
|
65
|
+
* single regex is the whole rule. JSON Schema regex follows ECMA 262, which
|
|
66
|
+
* supports lookaheads, so the spec can embed this string directly.
|
|
67
|
+
*
|
|
68
|
+
* Length cap is enforced separately because JSON Schema's `maxLength` is
|
|
69
|
+
* the canonical place to express it (vs. cramming `{1,64}` into the regex).
|
|
70
|
+
*/
|
|
71
|
+
export declare const SKILL_ID_PATTERN: RegExp;
|
|
72
|
+
export declare const SKILL_ID_MAX_LENGTH = 64;
|
|
73
|
+
/** True when `id` is a structurally valid skill ID under the rules above. */
|
|
74
|
+
export declare function isValidSkillId(id: string): boolean;
|
|
27
75
|
/** A skill groups related tools with shared config requirements */
|
|
28
76
|
export interface Skill {
|
|
29
77
|
id: string;
|
|
@@ -69,9 +117,17 @@ export type NodeSources = _Source[] | {
|
|
|
69
117
|
sources: _Source[];
|
|
70
118
|
};
|
|
71
119
|
/** Kind of evaluator. See {@link Evaluator}. */
|
|
72
|
-
export
|
|
120
|
+
export declare const EVALUATOR_KINDS: readonly ["value", "function", "judge"];
|
|
121
|
+
export type EvaluatorKind = (typeof EVALUATOR_KINDS)[number];
|
|
73
122
|
/** Aggregation policy for a node's evaluator results. v1 implements `all_pass`. */
|
|
74
|
-
export
|
|
123
|
+
export declare const EVAL_POLICIES: readonly ["all_pass", "any_pass", "weighted"];
|
|
124
|
+
export type EvalPolicy = (typeof EVAL_POLICIES)[number];
|
|
125
|
+
/** Action when a `requires` precondition fails. */
|
|
126
|
+
export declare const REQUIRES_ON_FAIL: readonly ["fail", "skip"];
|
|
127
|
+
export type RequiresOnFail = (typeof REQUIRES_ON_FAIL)[number];
|
|
128
|
+
/** MCP server transport type. Inferred from command/url when omitted. */
|
|
129
|
+
export declare const MCP_TRANSPORTS: readonly ["stdio", "http"];
|
|
130
|
+
export type McpTransport = (typeof MCP_TRANSPORTS)[number];
|
|
75
131
|
/**
|
|
76
132
|
* The deterministic rule body for a `value` or `function` evaluator.
|
|
77
133
|
*
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill categories: used for per-node validation and grouping.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for runtime + compile-time. The `as const` tuple
|
|
5
|
+
* lets the CLI iterate it for help text and the loader use it for runtime
|
|
6
|
+
* validation; `SkillCategory` is derived so adding a category requires
|
|
7
|
+
* only this line.
|
|
8
|
+
*/
|
|
9
|
+
export const SKILL_CATEGORIES = ["general", "git", "tasks", "notification", "observability", "data"];
|
|
10
|
+
/**
|
|
11
|
+
* Skill harness directories, listed in ascending priority order.
|
|
12
|
+
*
|
|
13
|
+
* Both the loader (which scans these directories last-wins for ID
|
|
14
|
+
* collisions) and the CLI (which writes scaffolds into one of them)
|
|
15
|
+
* must agree on this list. A divergence means scaffolded skills land
|
|
16
|
+
* somewhere the loader doesn't read, or the loader reads from a
|
|
17
|
+
* directory the CLI can't target.
|
|
18
|
+
*
|
|
19
|
+
* Order matters: the loader treats later entries as higher priority,
|
|
20
|
+
* so `.sweny/skills/` overrides `.claude/skills/` on a name collision.
|
|
21
|
+
*/
|
|
22
|
+
export const SKILL_HARNESSES = [
|
|
23
|
+
{ key: "gemini", path: ".gemini/skills" },
|
|
24
|
+
{ key: "agents", path: ".agents/skills" },
|
|
25
|
+
{ key: "claude", path: ".claude/skills" },
|
|
26
|
+
{ key: "sweny", path: ".sweny/skills" },
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Skill ID validation rules, declared once for runtime + spec.
|
|
30
|
+
*
|
|
31
|
+
* The pattern excludes consecutive hyphens via a negative lookahead so a
|
|
32
|
+
* single regex is the whole rule. JSON Schema regex follows ECMA 262, which
|
|
33
|
+
* supports lookaheads, so the spec can embed this string directly.
|
|
34
|
+
*
|
|
35
|
+
* Length cap is enforced separately because JSON Schema's `maxLength` is
|
|
36
|
+
* the canonical place to express it (vs. cramming `{1,64}` into the regex).
|
|
37
|
+
*/
|
|
38
|
+
export const SKILL_ID_PATTERN = /^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
39
|
+
export const SKILL_ID_MAX_LENGTH = 64;
|
|
40
|
+
/** True when `id` is a structurally valid skill ID under the rules above. */
|
|
41
|
+
export function isValidSkillId(id) {
|
|
42
|
+
return typeof id === "string" && id.length > 0 && id.length <= SKILL_ID_MAX_LENGTH && SKILL_ID_PATTERN.test(id);
|
|
43
|
+
}
|
|
44
|
+
/** Kind of evaluator. See {@link Evaluator}. */
|
|
45
|
+
export const EVALUATOR_KINDS = ["value", "function", "judge"];
|
|
46
|
+
/** Aggregation policy for a node's evaluator results. v1 implements `all_pass`. */
|
|
47
|
+
export const EVAL_POLICIES = ["all_pass", "any_pass", "weighted"];
|
|
48
|
+
/** Action when a `requires` precondition fails. */
|
|
49
|
+
export const REQUIRES_ON_FAIL = ["fail", "skip"];
|
|
50
|
+
/** MCP server transport type. Inferred from command/url when omitted. */
|
|
51
|
+
export const MCP_TRANSPORTS = ["stdio", "http"];
|
|
1
52
|
export const consoleLogger = {
|
|
2
53
|
info: (msg, data) => console.log(`[info] ${msg}`, data ?? ""),
|
|
3
54
|
warn: (msg, data) => console.warn(`[warn] ${msg}`, data ?? ""),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweny-ai/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sweny": "./dist/cli/main.js"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
51
|
"test": "vitest run --exclude 'src/__tests__/integration/**' --exclude 'dist/**'",
|
|
52
52
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
53
|
-
"check:schema-drift": "node scripts/write-public-schema.mjs && git diff --exit-code -- ../../spec/public/schemas/workflow.json"
|
|
53
|
+
"check:schema-drift": "node scripts/write-public-schema.mjs && git diff --exit-code -- ../../spec/public/schemas/workflow.json ../../spec/public/schemas/skill.json"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@anthropic-ai/claude-agent-sdk": "^0.2.114",
|