@yolk-sdk/skillset 0.0.1-canary.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yolk SDK contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @yolk-sdk/skillset
2
+
3
+ Domain-free parsing and catalog primitives for portable skills and slash commands.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @yolk-sdk/skillset@canary effect
9
+ ```
10
+
11
+ Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
12
+
13
+ ## Subpaths
14
+
15
+ | Subpath | Purpose |
16
+ | --- | --- |
17
+ | `@yolk-sdk/skillset` | Skill markdown, command markdown, manifest, and merge APIs |
18
+
19
+ ## Imports
20
+
21
+ ```ts
22
+ import {
23
+ formatAvailableSkills,
24
+ mergeSkillsets,
25
+ parseCommandMarkdown,
26
+ parseSkillMarkdown,
27
+ renderCommand,
28
+ validateSkillsetName
29
+ } from '@yolk-sdk/skillset'
30
+ ```
31
+
32
+ ## Skill markdown
33
+
34
+ ```md
35
+ ---
36
+ name: web-search
37
+ description: Search the web. Use when current public web context is needed.
38
+ ---
39
+
40
+ # Web Search
41
+
42
+ Instructions...
43
+ ```
44
+
45
+ ```ts
46
+ const skill = parseSkillMarkdown({ path: 'web-search/SKILL.md', content })
47
+ ```
48
+
49
+ ## Command markdown
50
+
51
+ ```md
52
+ ---
53
+ description: Run a guided package release
54
+ ---
55
+
56
+ First load the package-release skill.
57
+
58
+ <user-request>
59
+ $ARGUMENTS
60
+ </user-request>
61
+ ```
62
+
63
+ ```ts
64
+ const command = parseCommandMarkdown({ path: 'package-release.md', content })
65
+ const prompt = renderCommand(command, 'publish first canary')
66
+ ```
67
+
68
+ ## Merge model
69
+
70
+ `mergeSkillsets` combines multiple host-provided sources deterministically. Hosts decide source priority and policy.
71
+
72
+ ## Host responsibilities
73
+
74
+ - Load skills/commands from filesystem, DB, KV, bundles, or remote sources.
75
+ - Enforce policy, permissions, runtime tool wiring, and UI.
76
+ - Decide when to inject `formatAvailableSkills` into model context.
77
+
78
+ ## Boundaries
79
+
80
+ - No filesystem, Next.js, Cloudflare, DB, auth, provider SDKs, or tool execution.
81
+ - Package owns schemas, markdown parsing, command rendering, manifests, and merge helpers only.
@@ -0,0 +1,56 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+ import * as Schema from "effect/Schema";
4
+
5
+ //#region src/command.d.ts
6
+ declare const CommandArgument: Schema.Struct<{
7
+ readonly name: Schema.String;
8
+ readonly required: Schema.Boolean;
9
+ readonly description: Schema.optional<Schema.String>;
10
+ }>;
11
+ type CommandArgument = typeof CommandArgument.Type;
12
+ declare const CommandAccess: Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>;
13
+ type CommandAccess = typeof CommandAccess.Type;
14
+ declare const CommandInfo: Schema.Struct<{
15
+ readonly name: Schema.String;
16
+ readonly description: Schema.optional<Schema.String>;
17
+ readonly template: Schema.String;
18
+ readonly hints: Schema.$Array<Schema.String>;
19
+ readonly arguments: Schema.optional<Schema.$Array<Schema.Struct<{
20
+ readonly name: Schema.String;
21
+ readonly required: Schema.Boolean;
22
+ readonly description: Schema.optional<Schema.String>;
23
+ }>>>;
24
+ readonly access: Schema.optional<Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>>;
25
+ readonly fileRefs: Schema.optional<Schema.Boolean>;
26
+ readonly location: Schema.optional<Schema.String>;
27
+ readonly source: Schema.optional<Schema.String>;
28
+ }>;
29
+ type CommandInfo = typeof CommandInfo.Type;
30
+ type ParseCommandInput = {
31
+ readonly markdown: string;
32
+ readonly name: string;
33
+ readonly location?: string;
34
+ readonly source?: string;
35
+ };
36
+ declare const commandHints: (template: string) => string[];
37
+ declare const parseCommandMarkdown: (input: ParseCommandInput) => Effect.Effect<{
38
+ location: string | undefined;
39
+ source: string | undefined;
40
+ fileRefs?: boolean | undefined;
41
+ access?: "read" | "write" | "destructive" | undefined;
42
+ arguments?: readonly {
43
+ readonly required: boolean;
44
+ readonly name: string;
45
+ readonly description?: string | undefined;
46
+ }[] | undefined;
47
+ name: string;
48
+ description: string | undefined;
49
+ template: string;
50
+ hints: string[];
51
+ }, SkillsetError, never>;
52
+ declare const parseCommandArguments: (input: string) => string[];
53
+ declare const renderCommand: (command: CommandInfo, argumentsText: string) => string;
54
+ //#endregion
55
+ export { CommandAccess, CommandArgument, CommandInfo, ParseCommandInput, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand };
56
+ //# sourceMappingURL=command.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.mts","names":[],"sources":["../src/command.ts"],"mappings":";;;;;cAMa,eAAA,EAAe,MAAA,CAAA,MAAA;EAAA;;;;KAKhB,eAAA,UAAyB,eAAA,CAAgB,IAAI;AAAA,cAE5C,aAAA,EAAa,MAAA,CAAA,KAAA,WAAA,MAAA,CAAA,OAAA,UAAA,MAAA,CAAA,OAAA,WAAA,MAAA,CAAA,OAAA;AAAA,KAKd,aAAA,UAAuB,aAAA,CAAc,IAAI;AAAA,cAExC,WAAA,EAAW,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;KAWZ,WAAA,UAAqB,WAAA,CAAY,IAAI;AAAA,KAErC,iBAAA;EAAA,SACD,QAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;AAAA,cAsEE,YAAA,GAAgB,QAAgB;AAAA,cAUhC,oBAAA,GAAwB,KAAA,EAAO,iBAAA,KAAiB,MAAA,CAAA,MAAA;;;;;;;;;;;;;;;cAsBhD,qBAAA,GAAyB,KAAa;AAAA,cAkCtC,aAAA,GAAiB,OAAA,EAAS,WAAW,EAAE,aAAA"}
@@ -0,0 +1,135 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { parseMarkdownDocument } from "./markdown.mjs";
3
+ import { validateSkillsetName } from "./name.mjs";
4
+ import { Effect } from "effect";
5
+ import * as Schema from "effect/Schema";
6
+ //#region src/command.ts
7
+ const CommandArgument = Schema.Struct({
8
+ name: Schema.String,
9
+ required: Schema.Boolean,
10
+ description: Schema.optional(Schema.String)
11
+ });
12
+ const CommandAccess = Schema.Union([
13
+ Schema.Literal("read"),
14
+ Schema.Literal("write"),
15
+ Schema.Literal("destructive")
16
+ ]);
17
+ const CommandInfo = Schema.Struct({
18
+ name: Schema.String,
19
+ description: Schema.optional(Schema.String),
20
+ template: Schema.String,
21
+ hints: Schema.Array(Schema.String),
22
+ arguments: Schema.optional(Schema.Array(CommandArgument)),
23
+ access: Schema.optional(CommandAccess),
24
+ fileRefs: Schema.optional(Schema.Boolean),
25
+ location: Schema.optional(Schema.String),
26
+ source: Schema.optional(Schema.String)
27
+ });
28
+ const numberedPlaceholderPattern = /\$(\d+)/g;
29
+ const parseCommandAccess = (value) => {
30
+ if (value === void 0 || value.length === 0) return Effect.succeed(void 0);
31
+ switch (value) {
32
+ case "read":
33
+ case "write":
34
+ case "destructive": return Effect.succeed(value);
35
+ default: return Effect.fail(new SkillsetError({
36
+ cause: "frontmatter_invalid",
37
+ message: "Command access must be read, write, or destructive"
38
+ }));
39
+ }
40
+ };
41
+ const parseBooleanField = (field, value) => {
42
+ if (value === void 0 || value.length === 0) return Effect.succeed(void 0);
43
+ switch (value) {
44
+ case "true": return Effect.succeed(true);
45
+ case "false": return Effect.succeed(false);
46
+ default: return Effect.fail(new SkillsetError({
47
+ cause: "frontmatter_invalid",
48
+ message: `${field} must be true or false`
49
+ }));
50
+ }
51
+ };
52
+ const parseArgumentToken = (token) => {
53
+ const trimmed = token.trim();
54
+ if (trimmed.length === 0) return;
55
+ const required = !trimmed.endsWith("?");
56
+ const name = required ? trimmed : trimmed.slice(0, -1);
57
+ return name.length === 0 ? void 0 : {
58
+ name,
59
+ required
60
+ };
61
+ };
62
+ const parseCommandArgumentsField = (value) => {
63
+ if (value === void 0 || value.length === 0) return [];
64
+ return value.split(",").flatMap((token) => {
65
+ const argument = parseArgumentToken(token);
66
+ return argument === void 0 ? [] : [argument];
67
+ });
68
+ };
69
+ const commandHints = (template) => {
70
+ const numbered = Array.from(template.matchAll(numberedPlaceholderPattern), (match) => `$${match[1]}`);
71
+ const unique = [...new Set(numbered)].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)));
72
+ return template.includes("$ARGUMENTS") ? [...unique, "$ARGUMENTS"] : unique;
73
+ };
74
+ const parseCommandMarkdown = (input) => Effect.gen(function* () {
75
+ const name = yield* validateSkillsetName(input.name);
76
+ const document = yield* parseMarkdownDocument(input.markdown);
77
+ const description = document.data.description;
78
+ const access = yield* parseCommandAccess(document.data.access);
79
+ const fileRefs = yield* parseBooleanField("fileRefs", document.data.fileRefs);
80
+ const commandArguments = parseCommandArgumentsField(document.data.arguments);
81
+ return {
82
+ name,
83
+ description: description === void 0 || description.length === 0 ? void 0 : description,
84
+ template: document.content,
85
+ hints: commandHints(document.content),
86
+ ...commandArguments.length === 0 ? {} : { arguments: commandArguments },
87
+ ...access === void 0 ? {} : { access },
88
+ ...fileRefs === void 0 ? {} : { fileRefs },
89
+ location: input.location,
90
+ source: input.source
91
+ };
92
+ });
93
+ const parseCommandArguments = (input) => {
94
+ const result = [];
95
+ let current = "";
96
+ let quote;
97
+ for (const char of input.trim()) {
98
+ if (char === "'" && quote !== "double") {
99
+ quote = quote === "single" ? void 0 : "single";
100
+ continue;
101
+ }
102
+ if (char === "\"" && quote !== "single") {
103
+ quote = quote === "double" ? void 0 : "double";
104
+ continue;
105
+ }
106
+ if (/\s/.test(char) && quote === void 0) {
107
+ if (current.length > 0) {
108
+ result.push(current);
109
+ current = "";
110
+ }
111
+ continue;
112
+ }
113
+ current = `${current}${char}`;
114
+ }
115
+ if (current.length > 0) result.push(current);
116
+ return result;
117
+ };
118
+ const renderCommand = (command, argumentsText) => {
119
+ const args = parseCommandArguments(argumentsText);
120
+ const placeholders = Array.from(command.template.matchAll(numberedPlaceholderPattern), (match) => Number(match[1]));
121
+ const lastPlaceholder = placeholders.reduce((max, value) => Math.max(max, value), 0);
122
+ const withNumbered = command.template.replace(numberedPlaceholderPattern, (_, index) => {
123
+ const position = Number(index);
124
+ const argIndex = position - 1;
125
+ if (argIndex >= args.length) return "";
126
+ return position === lastPlaceholder ? args.slice(argIndex).join(" ") : args[argIndex] ?? "";
127
+ });
128
+ const usesArguments = command.template.includes("$ARGUMENTS");
129
+ const rendered = withNumbered.replaceAll("$ARGUMENTS", argumentsText);
130
+ return placeholders.length === 0 && !usesArguments && argumentsText.trim().length > 0 ? `${rendered}\n\n${argumentsText}`.trim() : rendered.trim();
131
+ };
132
+ //#endregion
133
+ export { CommandAccess, CommandArgument, CommandInfo, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand };
134
+
135
+ //# sourceMappingURL=command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.mjs","names":[],"sources":["../src/command.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { SkillsetError } from './errors.ts'\nimport { parseMarkdownDocument } from './markdown.ts'\nimport { validateSkillsetName } from './name.ts'\n\nexport const CommandArgument = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n description: Schema.optional(Schema.String)\n})\nexport type CommandArgument = typeof CommandArgument.Type\n\nexport const CommandAccess = Schema.Union([\n Schema.Literal('read'),\n Schema.Literal('write'),\n Schema.Literal('destructive')\n])\nexport type CommandAccess = typeof CommandAccess.Type\n\nexport const CommandInfo = Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n template: Schema.String,\n hints: Schema.Array(Schema.String),\n arguments: Schema.optional(Schema.Array(CommandArgument)),\n access: Schema.optional(CommandAccess),\n fileRefs: Schema.optional(Schema.Boolean),\n location: Schema.optional(Schema.String),\n source: Schema.optional(Schema.String)\n})\nexport type CommandInfo = typeof CommandInfo.Type\n\nexport type ParseCommandInput = {\n readonly markdown: string\n readonly name: string\n readonly location?: string\n readonly source?: string\n}\n\nconst numberedPlaceholderPattern = /\\$(\\d+)/g\n\nconst parseCommandAccess = (value: string | undefined) => {\n if (value === undefined || value.length === 0) {\n return Effect.succeed(undefined)\n }\n\n switch (value) {\n case 'read':\n case 'write':\n case 'destructive':\n return Effect.succeed(value)\n default:\n return Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Command access must be read, write, or destructive'\n })\n )\n }\n}\n\nconst parseBooleanField = (field: string, value: string | undefined) => {\n if (value === undefined || value.length === 0) {\n return Effect.succeed(undefined)\n }\n\n switch (value) {\n case 'true':\n return Effect.succeed(true)\n case 'false':\n return Effect.succeed(false)\n default:\n return Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: `${field} must be true or false`\n })\n )\n }\n}\n\nconst parseArgumentToken = (token: string) => {\n const trimmed = token.trim()\n\n if (trimmed.length === 0) {\n return undefined\n }\n\n const required = !trimmed.endsWith('?')\n const name = required ? trimmed : trimmed.slice(0, -1)\n\n return name.length === 0 ? undefined : { name, required }\n}\n\nconst parseCommandArgumentsField = (value: string | undefined): ReadonlyArray<CommandArgument> => {\n if (value === undefined || value.length === 0) {\n return []\n }\n\n return value.split(',').flatMap(token => {\n const argument = parseArgumentToken(token)\n\n return argument === undefined ? [] : [argument]\n })\n}\n\nexport const commandHints = (template: string) => {\n const numbered = Array.from(\n template.matchAll(numberedPlaceholderPattern),\n match => `$${match[1]}`\n )\n const unique = [...new Set(numbered)].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)))\n\n return template.includes('$ARGUMENTS') ? [...unique, '$ARGUMENTS'] : unique\n}\n\nexport const parseCommandMarkdown = (input: ParseCommandInput) =>\n Effect.gen(function* () {\n const name = yield* validateSkillsetName(input.name)\n const document = yield* parseMarkdownDocument(input.markdown)\n const description = document.data.description\n const access = yield* parseCommandAccess(document.data.access)\n const fileRefs = yield* parseBooleanField('fileRefs', document.data.fileRefs)\n const commandArguments = parseCommandArgumentsField(document.data.arguments)\n\n return {\n name,\n description: description === undefined || description.length === 0 ? undefined : description,\n template: document.content,\n hints: commandHints(document.content),\n ...(commandArguments.length === 0 ? {} : { arguments: commandArguments }),\n ...(access === undefined ? {} : { access }),\n ...(fileRefs === undefined ? {} : { fileRefs }),\n location: input.location,\n source: input.source\n }\n })\n\nexport const parseCommandArguments = (input: string) => {\n const result: string[] = []\n let current = ''\n let quote: 'single' | 'double' | undefined\n\n for (const char of input.trim()) {\n if (char === \"'\" && quote !== 'double') {\n quote = quote === 'single' ? undefined : 'single'\n continue\n }\n\n if (char === '\"' && quote !== 'single') {\n quote = quote === 'double' ? undefined : 'double'\n continue\n }\n\n if (/\\s/.test(char) && quote === undefined) {\n if (current.length > 0) {\n result.push(current)\n current = ''\n }\n continue\n }\n\n current = `${current}${char}`\n }\n\n if (current.length > 0) {\n result.push(current)\n }\n\n return result\n}\n\nexport const renderCommand = (command: CommandInfo, argumentsText: string) => {\n const args = parseCommandArguments(argumentsText)\n const placeholders = Array.from(command.template.matchAll(numberedPlaceholderPattern), match =>\n Number(match[1])\n )\n const lastPlaceholder = placeholders.reduce((max, value) => Math.max(max, value), 0)\n const withNumbered = command.template.replace(numberedPlaceholderPattern, (_, index: string) => {\n const position = Number(index)\n const argIndex = position - 1\n\n if (argIndex >= args.length) {\n return ''\n }\n\n return position === lastPlaceholder ? args.slice(argIndex).join(' ') : (args[argIndex] ?? '')\n })\n const usesArguments = command.template.includes('$ARGUMENTS')\n const rendered = withNumbered.replaceAll('$ARGUMENTS', argumentsText)\n\n return placeholders.length === 0 && !usesArguments && argumentsText.trim().length > 0\n ? `${rendered}\\n\\n${argumentsText}`.trim()\n : rendered.trim()\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ //#region src/errors.d.ts
4
+ declare const SkillsetErrorCause: Schema.Literals<readonly ["duplicate_entry", "frontmatter_field_missing", "frontmatter_invalid", "frontmatter_missing", "invalid_name", "name_mismatch"]>;
5
+ type SkillsetErrorCause = typeof SkillsetErrorCause.Type;
6
+ declare const SkillsetError_base: Schema.Class<SkillsetError, Schema.TaggedStruct<"SkillsetError", {
7
+ readonly cause: Schema.Literals<readonly ["duplicate_entry", "frontmatter_field_missing", "frontmatter_invalid", "frontmatter_missing", "invalid_name", "name_mismatch"]>;
8
+ readonly message: Schema.String;
9
+ }>, import("effect/Cause").YieldableError>;
10
+ declare class SkillsetError extends SkillsetError_base {}
11
+ //#endregion
12
+ export { SkillsetError, SkillsetErrorCause };
13
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;cAEa,kBAAA,EAAkB,MAAA,CAAA,QAAA;AAAA,KAQnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;AAAA,cAAA,kBAAA;;;;cAElD,aAAA,SAAsB,kBAGjC"}
@@ -0,0 +1,18 @@
1
+ import * as Schema from "effect/Schema";
2
+ //#region src/errors.ts
3
+ const SkillsetErrorCause = Schema.Literals([
4
+ "duplicate_entry",
5
+ "frontmatter_field_missing",
6
+ "frontmatter_invalid",
7
+ "frontmatter_missing",
8
+ "invalid_name",
9
+ "name_mismatch"
10
+ ]);
11
+ var SkillsetError = class extends Schema.TaggedErrorClass()("SkillsetError", {
12
+ cause: SkillsetErrorCause,
13
+ message: Schema.String
14
+ }) {};
15
+ //#endregion
16
+ export { SkillsetError, SkillsetErrorCause };
17
+
18
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import * as Schema from 'effect/Schema'\n\nexport const SkillsetErrorCause = Schema.Literals([\n 'duplicate_entry',\n 'frontmatter_field_missing',\n 'frontmatter_invalid',\n 'frontmatter_missing',\n 'invalid_name',\n 'name_mismatch'\n])\nexport type SkillsetErrorCause = typeof SkillsetErrorCause.Type\n\nexport class SkillsetError extends Schema.TaggedErrorClass<SkillsetError>()('SkillsetError', {\n cause: SkillsetErrorCause,\n message: Schema.String\n}) {}\n"],"mappings":";;AAEA,MAAa,qBAAqB,OAAO,SAAS;CAChD;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAGD,IAAa,gBAAb,cAAmC,OAAO,iBAAgC,EAAE,iBAAiB;CAC3F,OAAO;CACP,SAAS,OAAO;AAClB,CAAC,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { SkillsetError, SkillsetErrorCause } from "./errors.mjs";
2
+ import { CommandAccess, CommandArgument, CommandInfo, ParseCommandInput, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand } from "./command.mjs";
3
+ import { isValidSkillsetName, validateDirectoryName, validateSkillsetName } from "./name.mjs";
4
+ import { MarkdownDocument, parseMarkdownDocument } from "./markdown.mjs";
5
+ import { ParseSkillInput, SkillInfo, formatAvailableSkills, parseSkillMarkdown } from "./skill.mjs";
6
+ import { SkillsetManifest, emptySkillsetManifest } from "./manifest.mjs";
7
+ import { MergedSkillset, SkillsetSource, mergeSkillsets } from "./merge.mjs";
8
+ export { CommandAccess, CommandArgument, CommandInfo, type MarkdownDocument, type MergedSkillset, type ParseCommandInput, type ParseSkillInput, SkillInfo, SkillsetError, SkillsetErrorCause, SkillsetManifest, type SkillsetSource, commandHints, emptySkillsetManifest, formatAvailableSkills, isValidSkillsetName, mergeSkillsets, parseCommandArguments, parseCommandMarkdown, parseMarkdownDocument, parseSkillMarkdown, renderCommand, validateDirectoryName, validateSkillsetName };
package/dist/index.mjs ADDED
@@ -0,0 +1,8 @@
1
+ import { SkillsetError, SkillsetErrorCause } from "./errors.mjs";
2
+ import { parseMarkdownDocument } from "./markdown.mjs";
3
+ import { isValidSkillsetName, validateDirectoryName, validateSkillsetName } from "./name.mjs";
4
+ import { CommandAccess, CommandArgument, CommandInfo, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand } from "./command.mjs";
5
+ import { SkillInfo, formatAvailableSkills, parseSkillMarkdown } from "./skill.mjs";
6
+ import { SkillsetManifest, emptySkillsetManifest } from "./manifest.mjs";
7
+ import { mergeSkillsets } from "./merge.mjs";
8
+ export { CommandAccess, CommandArgument, CommandInfo, SkillInfo, SkillsetError, SkillsetErrorCause, SkillsetManifest, commandHints, emptySkillsetManifest, formatAvailableSkills, isValidSkillsetName, mergeSkillsets, parseCommandArguments, parseCommandMarkdown, parseMarkdownDocument, parseSkillMarkdown, renderCommand, validateDirectoryName, validateSkillsetName };
@@ -0,0 +1,33 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ //#region src/manifest.d.ts
4
+ declare const SkillsetManifest: Schema.Struct<{
5
+ readonly version: Schema.Literal<1>;
6
+ readonly skills: Schema.$Array<Schema.Struct<{
7
+ readonly name: Schema.String;
8
+ readonly description: Schema.String;
9
+ readonly location: Schema.String;
10
+ readonly content: Schema.String;
11
+ readonly source: Schema.optional<Schema.String>;
12
+ }>>;
13
+ readonly commands: Schema.$Array<Schema.Struct<{
14
+ readonly name: Schema.String;
15
+ readonly description: Schema.optional<Schema.String>;
16
+ readonly template: Schema.String;
17
+ readonly hints: Schema.$Array<Schema.String>;
18
+ readonly arguments: Schema.optional<Schema.$Array<Schema.Struct<{
19
+ readonly name: Schema.String;
20
+ readonly required: Schema.Boolean;
21
+ readonly description: Schema.optional<Schema.String>;
22
+ }>>>;
23
+ readonly access: Schema.optional<Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>>;
24
+ readonly fileRefs: Schema.optional<Schema.Boolean>;
25
+ readonly location: Schema.optional<Schema.String>;
26
+ readonly source: Schema.optional<Schema.String>;
27
+ }>>;
28
+ }>;
29
+ type SkillsetManifest = typeof SkillsetManifest.Type;
30
+ declare const emptySkillsetManifest: SkillsetManifest;
31
+ //#endregion
32
+ export { SkillsetManifest, emptySkillsetManifest };
33
+ //# sourceMappingURL=manifest.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.mts","names":[],"sources":["../src/manifest.ts"],"mappings":";;;cAIa,gBAAA,EAAgB,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;KAKjB,gBAAA,UAA0B,gBAAA,CAAiB,IAAI;AAAA,cAE9C,qBAAA,EAAuB,gBAInC"}
@@ -0,0 +1,18 @@
1
+ import { CommandInfo } from "./command.mjs";
2
+ import { SkillInfo } from "./skill.mjs";
3
+ import * as Schema from "effect/Schema";
4
+ //#region src/manifest.ts
5
+ const SkillsetManifest = Schema.Struct({
6
+ version: Schema.Literal(1),
7
+ skills: Schema.Array(SkillInfo),
8
+ commands: Schema.Array(CommandInfo)
9
+ });
10
+ const emptySkillsetManifest = {
11
+ version: 1,
12
+ skills: [],
13
+ commands: []
14
+ };
15
+ //#endregion
16
+ export { SkillsetManifest, emptySkillsetManifest };
17
+
18
+ //# sourceMappingURL=manifest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.mjs","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["import * as Schema from 'effect/Schema'\nimport { CommandInfo } from './command.ts'\nimport { SkillInfo } from './skill.ts'\n\nexport const SkillsetManifest = Schema.Struct({\n version: Schema.Literal(1),\n skills: Schema.Array(SkillInfo),\n commands: Schema.Array(CommandInfo)\n})\nexport type SkillsetManifest = typeof SkillsetManifest.Type\n\nexport const emptySkillsetManifest: SkillsetManifest = {\n version: 1,\n skills: [],\n commands: []\n}\n"],"mappings":";;;;AAIA,MAAa,mBAAmB,OAAO,OAAO;CAC5C,SAAS,OAAO,QAAQ,CAAC;CACzB,QAAQ,OAAO,MAAM,SAAS;CAC9B,UAAU,OAAO,MAAM,WAAW;AACpC,CAAC;AAGD,MAAa,wBAA0C;CACrD,SAAS;CACT,QAAQ,CAAC;CACT,UAAU,CAAC;AACb"}
@@ -0,0 +1,17 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+
4
+ //#region src/markdown.d.ts
5
+ type MarkdownDocument = {
6
+ readonly data: Readonly<Record<string, string>>;
7
+ readonly content: string;
8
+ };
9
+ declare const parseMarkdownDocument: (markdown: string) => Effect.Effect<{
10
+ data: {
11
+ [k: string]: string;
12
+ };
13
+ content: string;
14
+ }, SkillsetError, never>;
15
+ //#endregion
16
+ export { MarkdownDocument, parseMarkdownDocument };
17
+ //# sourceMappingURL=markdown.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.mts","names":[],"sources":["../src/markdown.ts"],"mappings":";;;;KAGY,gBAAA;EAAA,SACD,IAAA,EAAM,QAAQ,CAAC,MAAA;EAAA,SACf,OAAA;AAAA;AAAA,cAoBE,qBAAA,GAAyB,QAAA,aAAgB,MAAA,CAAA,MAAA;;;;;GAgDlD,aAAA"}
@@ -0,0 +1,41 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+ //#region src/markdown.ts
4
+ const frontmatterStart = "---\n";
5
+ const frontmatterEndIndex = (markdown) => markdown.indexOf("\n---", 4);
6
+ const parseFrontmatterLine = (line) => {
7
+ const separatorIndex = line.indexOf(":");
8
+ if (separatorIndex === -1) return;
9
+ const key = line.slice(0, separatorIndex).trim();
10
+ const value = line.slice(separatorIndex + 1).trim();
11
+ return key.length === 0 ? void 0 : {
12
+ key,
13
+ value
14
+ };
15
+ };
16
+ const parseMarkdownDocument = (markdown) => Effect.gen(function* () {
17
+ if (!markdown.startsWith(frontmatterStart)) return yield* Effect.fail(new SkillsetError({
18
+ cause: "frontmatter_missing",
19
+ message: "Markdown must start with YAML frontmatter"
20
+ }));
21
+ const endIndex = frontmatterEndIndex(markdown);
22
+ if (endIndex === -1) return yield* Effect.fail(new SkillsetError({
23
+ cause: "frontmatter_invalid",
24
+ message: "Markdown frontmatter is missing closing delimiter"
25
+ }));
26
+ const entries = markdown.slice(4, endIndex).split("\n").filter((line) => line.trim().length > 0).map(parseFrontmatterLine);
27
+ if (entries.some((entry) => entry === void 0)) return yield* Effect.fail(new SkillsetError({
28
+ cause: "frontmatter_invalid",
29
+ message: "Markdown frontmatter only supports simple key: value fields"
30
+ }));
31
+ const data = Object.fromEntries(entries.flatMap((entry) => entry === void 0 ? [] : [[entry.key, entry.value]]));
32
+ const contentStart = markdown.startsWith("\n", endIndex + 4) ? endIndex + 4 + 1 : endIndex + 4;
33
+ return {
34
+ data,
35
+ content: markdown.slice(contentStart).trim()
36
+ };
37
+ });
38
+ //#endregion
39
+ export { parseMarkdownDocument };
40
+
41
+ //# sourceMappingURL=markdown.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.mjs","names":[],"sources":["../src/markdown.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport { SkillsetError } from './errors.ts'\n\nexport type MarkdownDocument = {\n readonly data: Readonly<Record<string, string>>\n readonly content: string\n}\n\nconst frontmatterStart = '---\\n'\n\nconst frontmatterEndIndex = (markdown: string) => markdown.indexOf('\\n---', frontmatterStart.length)\n\nconst parseFrontmatterLine = (line: string) => {\n const separatorIndex = line.indexOf(':')\n\n if (separatorIndex === -1) {\n return undefined\n }\n\n const key = line.slice(0, separatorIndex).trim()\n const value = line.slice(separatorIndex + 1).trim()\n\n return key.length === 0 ? undefined : { key, value }\n}\n\nexport const parseMarkdownDocument = (markdown: string) =>\n Effect.gen(function* () {\n if (!markdown.startsWith(frontmatterStart)) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_missing',\n message: 'Markdown must start with YAML frontmatter'\n })\n )\n }\n\n const endIndex = frontmatterEndIndex(markdown)\n\n if (endIndex === -1) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Markdown frontmatter is missing closing delimiter'\n })\n )\n }\n\n const entries = markdown\n .slice(frontmatterStart.length, endIndex)\n .split('\\n')\n .filter(line => line.trim().length > 0)\n .map(parseFrontmatterLine)\n\n if (entries.some(entry => entry === undefined)) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Markdown frontmatter only supports simple key: value fields'\n })\n )\n }\n\n const data = Object.fromEntries(\n entries.flatMap(entry => (entry === undefined ? [] : [[entry.key, entry.value]]))\n )\n const contentStart = markdown.startsWith('\\n', endIndex + frontmatterStart.length)\n ? endIndex + frontmatterStart.length + 1\n : endIndex + frontmatterStart.length\n\n return {\n data,\n content: markdown.slice(contentStart).trim()\n }\n })\n"],"mappings":";;;AAQA,MAAM,mBAAmB;AAEzB,MAAM,uBAAuB,aAAqB,SAAS,QAAQ,SAAS,CAAuB;AAEnG,MAAM,wBAAwB,SAAiB;CAC7C,MAAM,iBAAiB,KAAK,QAAQ,GAAG;CAEvC,IAAI,mBAAmB,IACrB;CAGF,MAAM,MAAM,KAAK,MAAM,GAAG,cAAc,EAAE,KAAK;CAC/C,MAAM,QAAQ,KAAK,MAAM,iBAAiB,CAAC,EAAE,KAAK;CAElD,OAAO,IAAI,WAAW,IAAI,KAAA,IAAY;EAAE;EAAK;CAAM;AACrD;AAEA,MAAa,yBAAyB,aACpC,OAAO,IAAI,aAAa;CACtB,IAAI,CAAC,SAAS,WAAW,gBAAgB,GACvC,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,WAAW,oBAAoB,QAAQ;CAE7C,IAAI,aAAa,IACf,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,UAAU,SACb,MAAM,GAAyB,QAAQ,EACvC,MAAM,IAAI,EACV,QAAO,SAAQ,KAAK,KAAK,EAAE,SAAS,CAAC,EACrC,IAAI,oBAAoB;CAE3B,IAAI,QAAQ,MAAK,UAAS,UAAU,KAAA,CAAS,GAC3C,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,OAAO,OAAO,YAClB,QAAQ,SAAQ,UAAU,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,KAAK,CAAC,CAAE,CAClF;CACA,MAAM,eAAe,SAAS,WAAW,MAAM,WAAW,CAAuB,IAC7E,WAAW,IAA0B,IACrC,WAAW;CAEf,OAAO;EACL;EACA,SAAS,SAAS,MAAM,YAAY,EAAE,KAAK;CAC7C;AACF,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { CommandInfo } from "./command.mjs";
3
+ import { SkillInfo } from "./skill.mjs";
4
+ import { SkillsetManifest } from "./manifest.mjs";
5
+ import { Effect } from "effect";
6
+
7
+ //#region src/merge.d.ts
8
+ type SkillsetSource = {
9
+ readonly id: string;
10
+ readonly manifest: SkillsetManifest;
11
+ };
12
+ type MergedSkillset = {
13
+ readonly skills: ReadonlyArray<SkillInfo>;
14
+ readonly commands: ReadonlyArray<CommandInfo>;
15
+ };
16
+ declare const mergeSkillsets: (sources: ReadonlyArray<SkillsetSource>) => Effect.Effect<{
17
+ skills: {
18
+ readonly description: string;
19
+ readonly name: string;
20
+ readonly location: string;
21
+ readonly content: string;
22
+ readonly source?: string | undefined;
23
+ }[];
24
+ commands: {
25
+ readonly name: string;
26
+ readonly hints: readonly string[];
27
+ readonly template: string;
28
+ readonly description?: string | undefined;
29
+ readonly arguments?: readonly {
30
+ readonly required: boolean;
31
+ readonly name: string;
32
+ readonly description?: string | undefined;
33
+ }[] | undefined;
34
+ readonly access?: "read" | "write" | "destructive" | undefined;
35
+ readonly fileRefs?: boolean | undefined;
36
+ readonly location?: string | undefined;
37
+ readonly source?: string | undefined;
38
+ }[];
39
+ }, SkillsetError, never>;
40
+ //#endregion
41
+ export { MergedSkillset, SkillsetSource, mergeSkillsets };
42
+ //# sourceMappingURL=merge.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.d.mts","names":[],"sources":["../src/merge.ts"],"mappings":";;;;;;;KAMY,cAAA;EAAA,SACD,EAAA;EAAA,SACA,QAAA,EAAU,gBAAgB;AAAA;AAAA,KAGzB,cAAA;EAAA,SACD,MAAA,EAAQ,aAAA,CAAc,SAAA;EAAA,SACtB,QAAA,EAAU,aAAA,CAAc,WAAA;AAAA;AAAA,cAuCtB,cAAA,GAAkB,OAAA,EAAS,aAAA,CAAc,cAAA,MAAe,MAAA,CAAA,MAAA"}
package/dist/merge.mjs ADDED
@@ -0,0 +1,32 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+ //#region src/merge.ts
4
+ const mergeByName = (entries) => {
5
+ const byName = /* @__PURE__ */ new Map();
6
+ [...entries].reverse().forEach((group) => {
7
+ group.forEach((entry) => byName.set(entry.name, entry));
8
+ });
9
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
10
+ };
11
+ const duplicateNames = (entries) => {
12
+ const names = entries.map((entry) => entry.name);
13
+ return [...new Set(names.filter((name, index) => names.indexOf(name) !== index))];
14
+ };
15
+ const rejectInternalDuplicates = (source) => {
16
+ const duplicates = [...duplicateNames(source.manifest.skills).map((name) => `skill:${name}`), ...duplicateNames(source.manifest.commands).map((name) => `command:${name}`)];
17
+ return duplicates.length === 0 ? Effect.void : Effect.fail(new SkillsetError({
18
+ cause: "duplicate_entry",
19
+ message: `Duplicate entries in source ${source.id}: ${duplicates.join(", ")}`
20
+ }));
21
+ };
22
+ const mergeSkillsets = (sources) => Effect.gen(function* () {
23
+ yield* Effect.forEach(sources, rejectInternalDuplicates, { discard: true });
24
+ return {
25
+ skills: mergeByName(sources.map((source) => source.manifest.skills)),
26
+ commands: mergeByName(sources.map((source) => source.manifest.commands))
27
+ };
28
+ });
29
+ //#endregion
30
+ export { mergeSkillsets };
31
+
32
+ //# sourceMappingURL=merge.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.mjs","names":[],"sources":["../src/merge.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport { SkillsetError } from './errors.ts'\nimport type { CommandInfo } from './command.ts'\nimport type { SkillInfo } from './skill.ts'\nimport type { SkillsetManifest } from './manifest.ts'\n\nexport type SkillsetSource = {\n readonly id: string\n readonly manifest: SkillsetManifest\n}\n\nexport type MergedSkillset = {\n readonly skills: ReadonlyArray<SkillInfo>\n readonly commands: ReadonlyArray<CommandInfo>\n}\n\nconst mergeByName = <Entry extends { readonly name: string }>(\n entries: ReadonlyArray<ReadonlyArray<Entry>>\n) => {\n const byName = new Map<string, Entry>()\n\n const reversedEntries = [...entries].reverse()\n\n reversedEntries.forEach(group => {\n group.forEach(entry => byName.set(entry.name, entry))\n })\n\n return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\nconst duplicateNames = <Entry extends { readonly name: string }>(entries: ReadonlyArray<Entry>) => {\n const names = entries.map(entry => entry.name)\n\n return [...new Set(names.filter((name, index) => names.indexOf(name) !== index))]\n}\n\nconst rejectInternalDuplicates = (source: SkillsetSource) => {\n const duplicates = [\n ...duplicateNames(source.manifest.skills).map(name => `skill:${name}`),\n ...duplicateNames(source.manifest.commands).map(name => `command:${name}`)\n ]\n\n return duplicates.length === 0\n ? Effect.void\n : Effect.fail(\n new SkillsetError({\n cause: 'duplicate_entry',\n message: `Duplicate entries in source ${source.id}: ${duplicates.join(', ')}`\n })\n )\n}\n\nexport const mergeSkillsets = (sources: ReadonlyArray<SkillsetSource>) =>\n Effect.gen(function* () {\n yield* Effect.forEach(sources, rejectInternalDuplicates, { discard: true })\n\n return {\n skills: mergeByName(sources.map(source => source.manifest.skills)),\n commands: mergeByName(sources.map(source => source.manifest.commands))\n }\n })\n"],"mappings":";;;AAgBA,MAAM,eACJ,YACG;CACH,MAAM,yBAAS,IAAI,IAAmB;CAItC,CAFyB,GAAG,OAAO,EAAE,QAEvB,EAAE,SAAQ,UAAS;EAC/B,MAAM,SAAQ,UAAS,OAAO,IAAI,MAAM,MAAM,KAAK,CAAC;CACtD,CAAC;CAED,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACzE;AAEA,MAAM,kBAA2D,YAAkC;CACjG,MAAM,QAAQ,QAAQ,KAAI,UAAS,MAAM,IAAI;CAE7C,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAK,CAAC,CAAC;AAClF;AAEA,MAAM,4BAA4B,WAA2B;CAC3D,MAAM,aAAa,CACjB,GAAG,eAAe,OAAO,SAAS,MAAM,EAAE,KAAI,SAAQ,SAAS,MAAM,GACrE,GAAG,eAAe,OAAO,SAAS,QAAQ,EAAE,KAAI,SAAQ,WAAW,MAAM,CAC3E;CAEA,OAAO,WAAW,WAAW,IACzB,OAAO,OACP,OAAO,KACL,IAAI,cAAc;EAChB,OAAO;EACP,SAAS,+BAA+B,OAAO,GAAG,IAAI,WAAW,KAAK,IAAI;CAC5E,CAAC,CACH;AACN;AAEA,MAAa,kBAAkB,YAC7B,OAAO,IAAI,aAAa;CACtB,OAAO,OAAO,QAAQ,SAAS,0BAA0B,EAAE,SAAS,KAAK,CAAC;CAE1E,OAAO;EACL,QAAQ,YAAY,QAAQ,KAAI,WAAU,OAAO,SAAS,MAAM,CAAC;EACjE,UAAU,YAAY,QAAQ,KAAI,WAAU,OAAO,SAAS,QAAQ,CAAC;CACvE;AACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+
4
+ //#region src/name.d.ts
5
+ declare const isValidSkillsetName: (name: string) => boolean;
6
+ declare const validateSkillsetName: (name: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
7
+ declare const validateDirectoryName: (expected: string, actual: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
8
+ //#endregion
9
+ export { isValidSkillsetName, validateDirectoryName, validateSkillsetName };
10
+ //# sourceMappingURL=name.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.d.mts","names":[],"sources":["../src/name.ts"],"mappings":";;;;cAMa,mBAAA,GAAuB,IAAY;AAAA,cAGnC,oBAAA,GAAwB,IAAA,aAAY,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA;AAAA,cAUpC,qBAAA,GAAyB,QAAA,UAAkB,MAAA,aAAc,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA"}
package/dist/name.mjs ADDED
@@ -0,0 +1,18 @@
1
+ import { SkillsetError } from "./errors.mjs";
2
+ import { Effect } from "effect";
3
+ //#region src/name.ts
4
+ const skillsetNamePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
5
+ const maxSkillsetNameLength = 64;
6
+ const isValidSkillsetName = (name) => name.length > 0 && name.length <= maxSkillsetNameLength && skillsetNamePattern.test(name);
7
+ const validateSkillsetName = (name) => isValidSkillsetName(name) ? Effect.succeed(name) : Effect.fail(new SkillsetError({
8
+ cause: "invalid_name",
9
+ message: `Invalid skillset entry name: ${name}`
10
+ }));
11
+ const validateDirectoryName = (expected, actual) => expected === actual ? Effect.succeed(expected) : Effect.fail(new SkillsetError({
12
+ cause: "name_mismatch",
13
+ message: `Skill name must match directory name: expected ${expected}, got ${actual}`
14
+ }));
15
+ //#endregion
16
+ export { isValidSkillsetName, validateDirectoryName, validateSkillsetName };
17
+
18
+ //# sourceMappingURL=name.mjs.map