@sweny-ai/core 0.1.89 → 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 +2 -9
- package/dist/cli/skill.js +8 -12
- package/dist/schema.d.ts +163 -6
- package/dist/schema.js +151 -9
- package/dist/skills/custom-loader.js +9 -6
- package/dist/types.d.ts +50 -2
- package/dist/types.js +42 -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,7 @@
|
|
|
14
14
|
* and `../skills/index.js`; this CLI is a thin authoring shell over them.
|
|
15
15
|
*/
|
|
16
16
|
import { Command } from "commander";
|
|
17
|
-
import { type SkillCategory } from "../types.js";
|
|
18
|
-
declare const HARNESS_DIRS: {
|
|
19
|
-
readonly claude: ".claude/skills";
|
|
20
|
-
readonly sweny: ".sweny/skills";
|
|
21
|
-
readonly agents: ".agents/skills";
|
|
22
|
-
readonly gemini: ".gemini/skills";
|
|
23
|
-
};
|
|
24
|
-
type HarnessKey = keyof typeof HARNESS_DIRS;
|
|
17
|
+
import { type SkillCategory, type SkillHarnessKey } from "../types.js";
|
|
25
18
|
/** Build the SKILL.md body for a fresh scaffold. Exported for tests. */
|
|
26
19
|
export declare function renderSkillTemplate(opts: {
|
|
27
20
|
id: string;
|
|
@@ -31,7 +24,7 @@ export declare function renderSkillTemplate(opts: {
|
|
|
31
24
|
interface NewOptions {
|
|
32
25
|
description?: string;
|
|
33
26
|
category?: string;
|
|
34
|
-
harness?:
|
|
27
|
+
harness?: SkillHarnessKey;
|
|
35
28
|
force?: boolean;
|
|
36
29
|
}
|
|
37
30
|
/**
|
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
|
-
import { SKILL_CATEGORIES } from "../types.js";
|
|
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;
|
|
@@ -87,7 +83,7 @@ export function renderSkillTemplate(opts) {
|
|
|
87
83
|
*/
|
|
88
84
|
export function runSkillNew(idArg, options, cwd = process.cwd()) {
|
|
89
85
|
const id = idArg.toLowerCase();
|
|
90
|
-
if (!
|
|
86
|
+
if (!isValidSkillId(id)) {
|
|
91
87
|
console.error(chalk.red(` Invalid skill id "${idArg}".\n Skill ids must be lowercase alphanumeric + hyphens (no consecutive hyphens), max 64 chars.`));
|
|
92
88
|
process.exit(2);
|
|
93
89
|
return;
|
|
@@ -102,7 +98,7 @@ export function runSkillNew(idArg, options, cwd = process.cwd()) {
|
|
|
102
98
|
const harness = options.harness ?? "claude";
|
|
103
99
|
const baseDir = HARNESS_DIRS[harness];
|
|
104
100
|
if (!baseDir) {
|
|
105
|
-
console.error(chalk.red(` Unknown harness "${harness}". Allowed: ${
|
|
101
|
+
console.error(chalk.red(` Unknown harness "${harness}". Allowed: ${HARNESS_KEYS.join(", ")}`));
|
|
106
102
|
process.exit(2);
|
|
107
103
|
return;
|
|
108
104
|
}
|
|
@@ -197,7 +193,7 @@ export function registerSkillCommand(program) {
|
|
|
197
193
|
.description("Scaffold a new SKILL.md in .claude/skills/<id>/")
|
|
198
194
|
.option("-d, --description <text>", "One-line description (required field in frontmatter)")
|
|
199
195
|
.option("-c, --category <category>", `Skill category (${SKILL_CATEGORIES.join("|")})`, "general")
|
|
200
|
-
.option("--harness <harness>", `Where to place the skill (${
|
|
196
|
+
.option("--harness <harness>", `Where to place the skill (${HARNESS_KEYS.join("|")})`, "claude")
|
|
201
197
|
.option("--force", "Overwrite an existing SKILL.md")
|
|
202
198
|
.action((id, options) => runSkillNew(id, options));
|
|
203
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: "general" | "git" | "tasks" | "notification" | "observability";
|
|
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: "general" | "git" | "tasks" | "notification" | "observability";
|
|
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: "general" | "git" | "tasks" | "notification" | "observability";
|
|
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: "general" | "git" | "tasks" | "notification" | "observability";
|
|
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,12 +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 } from "../types.js";
|
|
14
|
+
import { SKILL_CATEGORIES, SKILL_HARNESSES, isValidSkillId } from "../types.js";
|
|
15
15
|
import { builtinSkills, isSkillConfigured } from "./index.js";
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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);
|
|
20
23
|
/**
|
|
21
24
|
* Discover custom skills and surface diagnostics for broken ones.
|
|
22
25
|
*
|
|
@@ -104,7 +107,7 @@ function parseSkillMd(content, path) {
|
|
|
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
|
@@ -32,6 +32,46 @@ export interface ConfigField {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare const SKILL_CATEGORIES: readonly ["general", "git", "tasks", "notification", "observability", "data"];
|
|
34
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;
|
|
35
75
|
/** A skill groups related tools with shared config requirements */
|
|
36
76
|
export interface Skill {
|
|
37
77
|
id: string;
|
|
@@ -77,9 +117,17 @@ export type NodeSources = _Source[] | {
|
|
|
77
117
|
sources: _Source[];
|
|
78
118
|
};
|
|
79
119
|
/** Kind of evaluator. See {@link Evaluator}. */
|
|
80
|
-
export
|
|
120
|
+
export declare const EVALUATOR_KINDS: readonly ["value", "function", "judge"];
|
|
121
|
+
export type EvaluatorKind = (typeof EVALUATOR_KINDS)[number];
|
|
81
122
|
/** Aggregation policy for a node's evaluator results. v1 implements `all_pass`. */
|
|
82
|
-
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];
|
|
83
131
|
/**
|
|
84
132
|
* The deterministic rule body for a `value` or `function` evaluator.
|
|
85
133
|
*
|
package/dist/types.js
CHANGED
|
@@ -7,6 +7,48 @@
|
|
|
7
7
|
* only this line.
|
|
8
8
|
*/
|
|
9
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"];
|
|
10
52
|
export const consoleLogger = {
|
|
11
53
|
info: (msg, data) => console.log(`[info] ${msg}`, data ?? ""),
|
|
12
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",
|