@graphorin/skills 0.5.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/anthropic-spec-snapshot.json +114 -0
- package/dist/activation/index.d.ts +30 -0
- package/dist/activation/index.d.ts.map +1 -0
- package/dist/activation/index.js +71 -0
- package/dist/activation/index.js.map +1 -0
- package/dist/errors/index.d.ts +110 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +126 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/frontmatter/index.d.ts +120 -0
- package/dist/frontmatter/index.d.ts.map +1 -0
- package/dist/frontmatter/index.js +451 -0
- package/dist/frontmatter/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/loader/index.d.ts +55 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +463 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/migration/index.d.ts +62 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +131 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/registry/bridge.d.ts +47 -0
- package/dist/registry/bridge.d.ts.map +1 -0
- package/dist/registry/bridge.js +91 -0
- package/dist/registry/bridge.js.map +1 -0
- package/dist/registry/index.d.ts +140 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +193 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/spec/index.d.ts +92 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +105 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/errors/index.ts"],"sourcesContent":["/**\n * Typed error hierarchy for `@graphorin/skills`.\n *\n * Every error carries a stable lowercase `kind` discriminator so the\n * agent runtime, the CLI, and the audit emitter can branch without\n * resorting to `instanceof` chains, plus an optional `hint` field with\n * an actionable remediation step (a CLI command or a documentation\n * link).\n *\n * @packageDocumentation\n */\n\n/** Base class for every error thrown by `@graphorin/skills`. */\nexport abstract class GraphorinSkillsError extends Error {\n abstract readonly kind: string;\n readonly hint?: string;\n override readonly cause?: unknown;\n constructor(message: string, options?: { hint?: string; cause?: unknown }) {\n super(message);\n this.name = new.target.name;\n if (options?.hint !== undefined) this.hint = options.hint;\n if (options?.cause !== undefined) this.cause = options.cause;\n }\n}\n\n/**\n * The frontmatter validator detected an Anthropic-base / `graphorin-*`\n * collision and the operator-resolved policy is `'error'`.\n */\nexport class SkillFrontmatterConflictError extends GraphorinSkillsError {\n readonly kind = 'frontmatter:conflict' as const;\n readonly skillName: string;\n readonly field: string;\n readonly conflictingFields: ReadonlyArray<string>;\n constructor(\n skillName: string,\n field: string,\n conflictingFields: ReadonlyArray<string>,\n options?: { hint?: string },\n ) {\n super(\n `Skill '${skillName}' frontmatter conflict on '${field}' (also set: ${conflictingFields\n .filter((f) => f !== field)\n .join(', ')}). Validator policy: 'error'.`,\n options,\n );\n this.skillName = skillName;\n this.field = field;\n this.conflictingFields = conflictingFields;\n }\n}\n\n/** A skill manifest could not be parsed (missing frontmatter / invalid YAML). */\nexport class SkillManifestParseError extends GraphorinSkillsError {\n readonly kind = 'manifest:parse' as const;\n}\n\n/**\n * The runtime-compat declaration on a skill does not satisfy the\n * loader's installed runtime version.\n */\nexport class SkillRuntimeCompatError extends GraphorinSkillsError {\n readonly kind = 'runtime-compat:mismatch' as const;\n readonly skillName: string;\n readonly declared: string;\n readonly installed: string;\n constructor(skillName: string, declared: string, installed: string, options?: { hint?: string }) {\n super(\n `Skill '${skillName}' declares runtime-compat '${declared}' which does not match installed Graphorin '${installed}'.`,\n options,\n );\n this.skillName = skillName;\n this.declared = declared;\n this.installed = installed;\n }\n}\n\n/** A required Anthropic-base field is missing from the frontmatter. */\nexport class SkillRequiredFieldMissingError extends GraphorinSkillsError {\n readonly kind = 'frontmatter:missing-required' as const;\n readonly field: string;\n constructor(field: string, options?: { hint?: string }) {\n super(`Skill frontmatter is missing required field '${field}'.`, options);\n this.field = field;\n }\n}\n\n/**\n * `Agent.toTool()` / `handoff(...)` would be invoked inside an\n * untrusted skill, but the skill did not declare\n * `graphorin-handoff-input-filter`. Throwing the error at activation\n * time prevents the runtime from materialising a sub-agent without an\n * explicit filter.\n */\nexport class InputFilterRequiredError extends GraphorinSkillsError {\n readonly kind = 'handoff:input-filter-required' as const;\n readonly skillName: string;\n constructor(skillName: string, options?: { hint?: string; cause?: unknown }) {\n super(\n `Untrusted skill '${skillName}' uses Agent.toTool()/handoff() but did not declare 'graphorin-handoff-input-filter'.`,\n {\n hint:\n options?.hint ??\n \"Add 'graphorin-handoff-input-filter: lastUser' (or another supported value) to the SKILL.md frontmatter.\",\n ...(options?.cause === undefined ? {} : { cause: options.cause }),\n },\n );\n this.skillName = skillName;\n }\n}\n\n/** A skill source could not be loaded from disk. */\nexport class SkillLoadError extends GraphorinSkillsError {\n readonly kind = 'load:failed' as const;\n readonly source: string;\n constructor(source: string, message: string, options?: { hint?: string; cause?: unknown }) {\n super(`Skill load failed (source='${source}'): ${message}`, options);\n this.source = source;\n }\n}\n\n/** A skill name in a registry registration collided with another already-loaded skill. */\nexport class SkillNameCollisionError extends GraphorinSkillsError {\n readonly kind = 'registry:name-collision' as const;\n readonly skillName: string;\n constructor(skillName: string, options?: { hint?: string; cause?: unknown }) {\n super(`Skill '${skillName}' is already registered.`, {\n hint:\n options?.hint ??\n 'Either rename one of the colliding skills or call `registry.unregister(name)` before re-registering.',\n ...(options?.cause === undefined ? {} : { cause: options.cause }),\n });\n this.skillName = skillName;\n }\n}\n\n/**\n * The slash-command parser received a string that did not match the\n * `/skill:<name>` grammar.\n */\nexport class SlashCommandParseError extends GraphorinSkillsError {\n readonly kind = 'slash:parse' as const;\n readonly raw: string;\n constructor(raw: string, message: string, options?: { hint?: string; cause?: unknown }) {\n super(`Slash command '${raw}': ${message}`, {\n hint: options?.hint ?? 'Use `/skill:<name>` to activate a registered skill.',\n ...(options?.cause === undefined ? {} : { cause: options.cause }),\n });\n this.raw = raw;\n }\n}\n\n/** Convenience union — every `kind` discriminator the package may emit. */\nexport type GraphorinSkillsErrorKind =\n | 'frontmatter:conflict'\n | 'manifest:parse'\n | 'runtime-compat:mismatch'\n | 'frontmatter:missing-required'\n | 'handoff:input-filter-required'\n | 'load:failed'\n | 'registry:name-collision'\n | 'slash:parse';\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAsB,uBAAtB,cAAmD,MAAM;CAEvD,AAAS;CACT,AAAkB;CAClB,YAAY,SAAiB,SAA8C;AACzE,QAAM,QAAQ;AACd,OAAK,OAAO,IAAI,OAAO;AACvB,MAAI,SAAS,SAAS,OAAW,MAAK,OAAO,QAAQ;AACrD,MAAI,SAAS,UAAU,OAAW,MAAK,QAAQ,QAAQ;;;;;;;AAQ3D,IAAa,gCAAb,cAAmD,qBAAqB;CACtE,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CACT,AAAS;CACT,YACE,WACA,OACA,mBACA,SACA;AACA,QACE,UAAU,UAAU,6BAA6B,MAAM,eAAe,kBACnE,QAAQ,MAAM,MAAM,MAAM,CAC1B,KAAK,KAAK,CAAC,gCACd,QACD;AACD,OAAK,YAAY;AACjB,OAAK,QAAQ;AACb,OAAK,oBAAoB;;;;AAK7B,IAAa,0BAAb,cAA6C,qBAAqB;CAChE,AAAS,OAAO;;;;;;AAOlB,IAAa,0BAAb,cAA6C,qBAAqB;CAChE,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CACT,AAAS;CACT,YAAY,WAAmB,UAAkB,WAAmB,SAA6B;AAC/F,QACE,UAAU,UAAU,6BAA6B,SAAS,8CAA8C,UAAU,KAClH,QACD;AACD,OAAK,YAAY;AACjB,OAAK,WAAW;AAChB,OAAK,YAAY;;;;AAKrB,IAAa,iCAAb,cAAoD,qBAAqB;CACvE,AAAS,OAAO;CAChB,AAAS;CACT,YAAY,OAAe,SAA6B;AACtD,QAAM,gDAAgD,MAAM,KAAK,QAAQ;AACzE,OAAK,QAAQ;;;;;;;;;;AAWjB,IAAa,2BAAb,cAA8C,qBAAqB;CACjE,AAAS,OAAO;CAChB,AAAS;CACT,YAAY,WAAmB,SAA8C;AAC3E,QACE,oBAAoB,UAAU,wFAC9B;GACE,MACE,SAAS,QACT;GACF,GAAI,SAAS,UAAU,SAAY,EAAE,GAAG,EAAE,OAAO,QAAQ,OAAO;GACjE,CACF;AACD,OAAK,YAAY;;;;AAKrB,IAAa,iBAAb,cAAoC,qBAAqB;CACvD,AAAS,OAAO;CAChB,AAAS;CACT,YAAY,QAAgB,SAAiB,SAA8C;AACzF,QAAM,8BAA8B,OAAO,MAAM,WAAW,QAAQ;AACpE,OAAK,SAAS;;;;AAKlB,IAAa,0BAAb,cAA6C,qBAAqB;CAChE,AAAS,OAAO;CAChB,AAAS;CACT,YAAY,WAAmB,SAA8C;AAC3E,QAAM,UAAU,UAAU,2BAA2B;GACnD,MACE,SAAS,QACT;GACF,GAAI,SAAS,UAAU,SAAY,EAAE,GAAG,EAAE,OAAO,QAAQ,OAAO;GACjE,CAAC;AACF,OAAK,YAAY;;;;;;;AAQrB,IAAa,yBAAb,cAA4C,qBAAqB;CAC/D,AAAS,OAAO;CAChB,AAAS;CACT,YAAY,KAAa,SAAiB,SAA8C;AACtF,QAAM,kBAAkB,IAAI,KAAK,WAAW;GAC1C,MAAM,SAAS,QAAQ;GACvB,GAAI,SAAS,UAAU,SAAY,EAAE,GAAG,EAAE,OAAO,QAAQ,OAAO;GACjE,CAAC;AACF,OAAK,MAAM"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { FieldResolution, FrontmatterDiagnostic, FrontmatterValidatorPolicy, HandoffInputFilterDeclaration, SkillToolDeclaration, UnknownFieldPolicy } from "../types/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/frontmatter/index.d.ts
|
|
4
|
+
|
|
5
|
+
/** Result of {@link splitSkillMd}. */
|
|
6
|
+
interface SplitSkillMd {
|
|
7
|
+
readonly frontmatter: string;
|
|
8
|
+
readonly body: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Split a raw SKILL.md string into the YAML frontmatter and the
|
|
12
|
+
* markdown body. The frontmatter delimiter is the canonical
|
|
13
|
+
* `---\n…\n---\n` pair.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
declare function splitSkillMd(skillMd: string): SplitSkillMd;
|
|
18
|
+
/**
|
|
19
|
+
* Parse the YAML frontmatter into a record. Returns `{}` for an empty
|
|
20
|
+
* block.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
declare function parseFrontmatterYaml(frontmatter: string): Record<string, unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a single field across the four field-resolution tiers.
|
|
27
|
+
* Returns the resolved value plus the source tier the value came from
|
|
28
|
+
* AND the list of conflicting source names so the validator can
|
|
29
|
+
* surface a structured diagnostic.
|
|
30
|
+
*
|
|
31
|
+
* @stable
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveSkillField<T = unknown>(frontmatter: Record<string, unknown>, field: string, fallback?: T): FieldResolution<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Options accepted by {@link validateFrontmatter}.
|
|
36
|
+
*
|
|
37
|
+
* @stable
|
|
38
|
+
*/
|
|
39
|
+
interface ValidateFrontmatterOptions {
|
|
40
|
+
/** Policy for direct collisions (Anthropic-base + `graphorin-*`). */
|
|
41
|
+
readonly conflictPolicy?: FrontmatterValidatorPolicy;
|
|
42
|
+
/** Policy for fields not part of the bundled snapshot or `graphorin-*` catalogue. */
|
|
43
|
+
readonly unknownFieldPolicy?: UnknownFieldPolicy;
|
|
44
|
+
/**
|
|
45
|
+
* Installed Graphorin runtime version. Used to validate
|
|
46
|
+
* `graphorin-runtime-compat` declarations against the running
|
|
47
|
+
* framework.
|
|
48
|
+
*/
|
|
49
|
+
readonly runtimeVersion?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Successful return of {@link validateFrontmatter}. */
|
|
52
|
+
interface ValidatedFrontmatter {
|
|
53
|
+
readonly raw: Readonly<Record<string, unknown>>;
|
|
54
|
+
readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;
|
|
55
|
+
readonly resolved: {
|
|
56
|
+
readonly name: FieldResolution<unknown>;
|
|
57
|
+
readonly description: FieldResolution<unknown>;
|
|
58
|
+
readonly license: FieldResolution<unknown>;
|
|
59
|
+
readonly compatibility: FieldResolution<unknown>;
|
|
60
|
+
readonly metadata: FieldResolution<unknown>;
|
|
61
|
+
readonly allowedTools: FieldResolution<unknown>;
|
|
62
|
+
readonly disableModelInvocation: FieldResolution<unknown>;
|
|
63
|
+
readonly trustLevel: FieldResolution<unknown>;
|
|
64
|
+
readonly runtimeCompat: FieldResolution<unknown>;
|
|
65
|
+
readonly sensitivity: FieldResolution<unknown>;
|
|
66
|
+
readonly sensitivityDefaults: FieldResolution<unknown>;
|
|
67
|
+
readonly sandbox: FieldResolution<unknown>;
|
|
68
|
+
readonly handoffInputFilter: FieldResolution<unknown>;
|
|
69
|
+
readonly anthropicSpec: FieldResolution<unknown>;
|
|
70
|
+
readonly version: FieldResolution<unknown>;
|
|
71
|
+
readonly tools: FieldResolution<unknown>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validate a parsed frontmatter against the bundled spec snapshot and
|
|
76
|
+
* the `graphorin-*` extension catalogue.
|
|
77
|
+
*
|
|
78
|
+
* @stable
|
|
79
|
+
*/
|
|
80
|
+
declare function validateFrontmatter(frontmatter: Record<string, unknown>, options?: ValidateFrontmatterOptions): ValidatedFrontmatter;
|
|
81
|
+
/**
|
|
82
|
+
* Parse the `allowed-tools` field. Accepts either a string (with
|
|
83
|
+
* whitespace-separated entries) or a string array. Returns `null` for
|
|
84
|
+
* unsupported shapes so the validator can attach a typed diagnostic.
|
|
85
|
+
*
|
|
86
|
+
* @stable
|
|
87
|
+
*/
|
|
88
|
+
declare function parseAllowedToolsValue(value: unknown): ReadonlyArray<string> | null;
|
|
89
|
+
/**
|
|
90
|
+
* Parse the `handoff-input-filter` field into a structured
|
|
91
|
+
* declaration. Returns `null` for unsupported shapes; callers should
|
|
92
|
+
* attach a diagnostic when the return value is `null` and the source
|
|
93
|
+
* value was non-undefined.
|
|
94
|
+
*
|
|
95
|
+
* @stable
|
|
96
|
+
*/
|
|
97
|
+
declare function parseHandoffInputFilter(value: unknown): HandoffInputFilterDeclaration | null;
|
|
98
|
+
/**
|
|
99
|
+
* Parse the `tools` field. Accepts either an array of strings (tool
|
|
100
|
+
* names — the loader resolves modules through naming convention) or
|
|
101
|
+
* an array of objects with `name`, `module`, `description`, `tags`.
|
|
102
|
+
* Returns `null` for unsupported shapes.
|
|
103
|
+
*
|
|
104
|
+
* @stable
|
|
105
|
+
*/
|
|
106
|
+
declare function parseToolsField(value: unknown): ReadonlyArray<SkillToolDeclaration> | null;
|
|
107
|
+
/**
|
|
108
|
+
* Best-effort semver-range satisfaction check. Supports the patterns
|
|
109
|
+
* the framework actually emits (`^x.y.z`, `~x.y.z`, `>=x.y.z`,
|
|
110
|
+
* `>x.y.z`, `<=x.y.z`, `<x.y.z`, plain `x.y.z`, the AND combinator
|
|
111
|
+
* with whitespace) without pulling a runtime dependency on `semver`.
|
|
112
|
+
* Unrecognised inputs return `false` so the validator emits a typed
|
|
113
|
+
* diagnostic.
|
|
114
|
+
*
|
|
115
|
+
* @stable
|
|
116
|
+
*/
|
|
117
|
+
declare function isRuntimeCompatSatisfied(range: string, version: string): boolean;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { SplitSkillMd, ValidateFrontmatterOptions, ValidatedFrontmatter, isRuntimeCompatSatisfied, parseAllowedToolsValue, parseFrontmatterYaml, parseHandoffInputFilter, parseToolsField, resolveSkillField, splitSkillMd, validateFrontmatter };
|
|
120
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/frontmatter/index.ts"],"sourcesContent":[],"mappings":";;;;;AAwK0B,UAlIT,YAAA,CAkIS;EACJ,SAAA,WAAA,EAAA,MAAA;EACM,SAAA,IAAA,EAAA,MAAA;;;;;;;;;AASK,iBAjIjB,YAAA,CAiIiB,OAAA,EAAA,MAAA,CAAA,EAjIc,YAiId;;;;;AAajC;;AAEW,iBA/HK,oBAAA,CA+HL,WAAA,EAAA,MAAA,CAAA,EA/HgD,MA+HhD,CAAA,MAAA,EAAA,OAAA,CAAA;;;AAoOX;AA0BA;AAmEA;AAqCA;;;iBA5cgB,4CACD,mDAEF,IACV,gBAAgB;;;;;;UAqDF,0BAAA;;4BAEW;;gCAEI;;;;;;;;;UAUf,oBAAA;gBACD,SAAS;wBACD,cAAc;;mBAEnB;0BACO;sBACJ;4BACM;uBACL;2BACI;qCACU;yBACZ;4BACG;0BACF;kCACQ;sBACZ;iCACW;4BACL;sBACN;oBACF;;;;;;;;;iBAUJ,mBAAA,cACD,mCACJ,6BACR;;;;;;;;iBAmOa,sBAAA,kBAAwC;;;;;;;;;iBA0BxC,uBAAA,kBAAyC;;;;;;;;;iBAmEzC,eAAA,kBAAiC,cAAc;;;;;;;;;;;iBAqC/C,wBAAA"}
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { SkillManifestParseError } from "../errors/index.js";
|
|
2
|
+
import { compareAuthorSpecHint, getGraphorinMapping, getKnownField, getSpecSnapshot } from "../spec/index.js";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
|
|
5
|
+
//#region src/frontmatter/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Frontmatter validator for `SKILL.md` files.
|
|
8
|
+
*
|
|
9
|
+
* Implements the field-resolution algorithm from ADR-043:
|
|
10
|
+
*
|
|
11
|
+
* 1. Anthropic-base (top-level, no prefix) — highest priority.
|
|
12
|
+
* 2. `metadata.graphorin.<field>` bucket per upstream `metadata`
|
|
13
|
+
* convention.
|
|
14
|
+
* 3. `graphorin-<field>` legacy top-level prefix.
|
|
15
|
+
* 4. caller-supplied fallback.
|
|
16
|
+
*
|
|
17
|
+
* The validator surfaces every diagnostic through the typed
|
|
18
|
+
* {@link FrontmatterDiagnostic} contract so callers can decide whether
|
|
19
|
+
* to log, fail, or escalate without re-parsing human strings.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Split a raw SKILL.md string into the YAML frontmatter and the
|
|
25
|
+
* markdown body. The frontmatter delimiter is the canonical
|
|
26
|
+
* `---\n…\n---\n` pair.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
function splitSkillMd(skillMd) {
|
|
31
|
+
const normalized = skillMd.replace(/^\uFEFF/u, "").replace(/\r\n/g, "\n");
|
|
32
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/u.exec(normalized);
|
|
33
|
+
if (match === null) throw new SkillManifestParseError("SKILL.md must begin with a YAML frontmatter block delimited by `---` lines.");
|
|
34
|
+
return {
|
|
35
|
+
frontmatter: match[1] ?? "",
|
|
36
|
+
body: match[2] ?? ""
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse the YAML frontmatter into a record. Returns `{}` for an empty
|
|
41
|
+
* block.
|
|
42
|
+
*
|
|
43
|
+
* @stable
|
|
44
|
+
*/
|
|
45
|
+
function parseFrontmatterYaml(frontmatter) {
|
|
46
|
+
if (frontmatter.trim().length === 0) return {};
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = parse(frontmatter);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new SkillManifestParseError("SKILL.md frontmatter is not valid YAML.", { cause: err });
|
|
52
|
+
}
|
|
53
|
+
if (parsed === null || parsed === void 0) return {};
|
|
54
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) throw new SkillManifestParseError(`Top-level SKILL.md frontmatter must be an object; got '${Array.isArray(parsed) ? "array" : typeof parsed}'.`);
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a single field across the four field-resolution tiers.
|
|
59
|
+
* Returns the resolved value plus the source tier the value came from
|
|
60
|
+
* AND the list of conflicting source names so the validator can
|
|
61
|
+
* surface a structured diagnostic.
|
|
62
|
+
*
|
|
63
|
+
* @stable
|
|
64
|
+
*/
|
|
65
|
+
function resolveSkillField(frontmatter, field, fallback) {
|
|
66
|
+
const conflictingSources = [];
|
|
67
|
+
const presentInBase = field in frontmatter;
|
|
68
|
+
const meta = frontmatter.metadata;
|
|
69
|
+
const presentInMeta = meta !== void 0 && meta !== null && typeof meta === "object" && !Array.isArray(meta) && `graphorin.${field}` in meta;
|
|
70
|
+
const presentInPrefix = `graphorin-${field}` in frontmatter;
|
|
71
|
+
if (presentInBase) conflictingSources.push(field);
|
|
72
|
+
if (presentInMeta) conflictingSources.push(`metadata.graphorin.${field}`);
|
|
73
|
+
if (presentInPrefix) conflictingSources.push(`graphorin-${field}`);
|
|
74
|
+
const conflicting = conflictingSources.length > 1;
|
|
75
|
+
if (presentInBase) return Object.freeze({
|
|
76
|
+
value: frontmatter[field],
|
|
77
|
+
source: "anthropic-base",
|
|
78
|
+
conflicting,
|
|
79
|
+
conflictingSources
|
|
80
|
+
});
|
|
81
|
+
if (presentInMeta) return Object.freeze({
|
|
82
|
+
value: meta[`graphorin.${field}`],
|
|
83
|
+
source: "metadata-graphorin",
|
|
84
|
+
conflicting,
|
|
85
|
+
conflictingSources
|
|
86
|
+
});
|
|
87
|
+
if (presentInPrefix) return Object.freeze({
|
|
88
|
+
value: frontmatter[`graphorin-${field}`],
|
|
89
|
+
source: "graphorin-prefix",
|
|
90
|
+
conflicting,
|
|
91
|
+
conflictingSources
|
|
92
|
+
});
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
value: fallback,
|
|
95
|
+
source: "fallback",
|
|
96
|
+
conflicting: false,
|
|
97
|
+
conflictingSources: []
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate a parsed frontmatter against the bundled spec snapshot and
|
|
102
|
+
* the `graphorin-*` extension catalogue.
|
|
103
|
+
*
|
|
104
|
+
* @stable
|
|
105
|
+
*/
|
|
106
|
+
function validateFrontmatter(frontmatter, options = {}) {
|
|
107
|
+
const conflictPolicy = options.conflictPolicy ?? "warn";
|
|
108
|
+
const unknownFieldPolicy = options.unknownFieldPolicy ?? "preserve";
|
|
109
|
+
const diagnostics = [];
|
|
110
|
+
const resolveAndDiag = (field, fallback) => {
|
|
111
|
+
const resolution = resolveSkillField(frontmatter, field, fallback);
|
|
112
|
+
if (resolution.conflicting) {
|
|
113
|
+
const message = `Both '${resolution.conflictingSources.join("' and '")}' are set for '${field}'. The Anthropic-base / metadata.graphorin.* tier wins; remove the lower-priority field to silence this diagnostic.`;
|
|
114
|
+
const hint = `Keep the highest-priority entry: '${resolution.source === "anthropic-base" ? field : resolution.source === "metadata-graphorin" ? `metadata.graphorin.${field}` : `graphorin-${field}`}'.`;
|
|
115
|
+
const severity = conflictPolicy === "error" ? "error" : "warn";
|
|
116
|
+
if (conflictPolicy !== "silent") diagnostics.push(Object.freeze({
|
|
117
|
+
kind: "conflict",
|
|
118
|
+
field,
|
|
119
|
+
severity,
|
|
120
|
+
message,
|
|
121
|
+
hint
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
return resolution;
|
|
125
|
+
};
|
|
126
|
+
const name = resolveAndDiag("name");
|
|
127
|
+
const description = resolveAndDiag("description");
|
|
128
|
+
const license = resolveAndDiag("license");
|
|
129
|
+
const compatibility = resolveAndDiag("compatibility");
|
|
130
|
+
const metadata = resolveAndDiag("metadata");
|
|
131
|
+
const allowedTools = resolveAndDiag("allowed-tools");
|
|
132
|
+
const disableModelInvocation = resolveAndDiag("disable-model-invocation", false);
|
|
133
|
+
const trustLevel = resolveAndDiag("trust-level");
|
|
134
|
+
const runtimeCompat = resolveAndDiag("runtime-compat");
|
|
135
|
+
const version = resolveAndDiag("version");
|
|
136
|
+
const sensitivity = resolveAndDiag("sensitivity");
|
|
137
|
+
const sensitivityDefaults = resolveAndDiag("sensitivity-defaults");
|
|
138
|
+
const sandbox = resolveAndDiag("sandbox");
|
|
139
|
+
const handoffInputFilter = resolveAndDiag("handoff-input-filter");
|
|
140
|
+
const anthropicSpec = resolveAndDiag("anthropic-spec");
|
|
141
|
+
const tools = resolveAndDiag("tools");
|
|
142
|
+
if (typeof name.value !== "string" || name.value.trim().length === 0) diagnostics.push(Object.freeze({
|
|
143
|
+
kind: "missing-required-field",
|
|
144
|
+
field: "name",
|
|
145
|
+
severity: "error",
|
|
146
|
+
message: "Required field 'name' is missing or empty.",
|
|
147
|
+
hint: "Add a unique 'name' (kebab-case is conventional) to the SKILL.md frontmatter."
|
|
148
|
+
}));
|
|
149
|
+
if (typeof description.value !== "string" || description.value.trim().length === 0) diagnostics.push(Object.freeze({
|
|
150
|
+
kind: "missing-required-field",
|
|
151
|
+
field: "description",
|
|
152
|
+
severity: "error",
|
|
153
|
+
message: "Required field 'description' is missing or empty.",
|
|
154
|
+
hint: "Add a description that explains when the model should activate the skill."
|
|
155
|
+
}));
|
|
156
|
+
if (allowedTools.value !== void 0) {
|
|
157
|
+
if (parseAllowedToolsValue(allowedTools.value) === null) diagnostics.push(Object.freeze({
|
|
158
|
+
kind: "invalid-field-type",
|
|
159
|
+
field: "allowed-tools",
|
|
160
|
+
severity: "warn",
|
|
161
|
+
message: "'allowed-tools' must be a string ('read_file write_file') or an array (['read_file', 'write_file']).",
|
|
162
|
+
hint: "Switch to one of the two supported shapes; entries are split on whitespace for the string form."
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
if (allowedTools.value !== void 0 && getKnownField("allowed-tools")?.stability === "experimental") diagnostics.push(Object.freeze({
|
|
166
|
+
kind: "experimental-field",
|
|
167
|
+
field: "allowed-tools",
|
|
168
|
+
severity: "info",
|
|
169
|
+
message: "'allowed-tools' is marked experimental in the bundled spec snapshot; behaviour may evolve."
|
|
170
|
+
}));
|
|
171
|
+
if (anthropicSpec.value !== void 0 && typeof anthropicSpec.value === "string") {
|
|
172
|
+
const compare = compareAuthorSpecHint(anthropicSpec.value);
|
|
173
|
+
if (compare === "newer") diagnostics.push(Object.freeze({
|
|
174
|
+
kind: "spec-newer-than-loader",
|
|
175
|
+
field: "anthropic-spec",
|
|
176
|
+
severity: "warn",
|
|
177
|
+
message: `Skill targets spec snapshot '${anthropicSpec.value}', newer than the bundled '${getSpecSnapshot().snapshotDate}'. Unknown fields will be preserved leniently.`
|
|
178
|
+
}));
|
|
179
|
+
else if (compare === "older") diagnostics.push(Object.freeze({
|
|
180
|
+
kind: "spec-older-than-loader",
|
|
181
|
+
field: "anthropic-spec",
|
|
182
|
+
severity: "info",
|
|
183
|
+
message: `Skill targets spec snapshot '${anthropicSpec.value}', older than the bundled '${getSpecSnapshot().snapshotDate}'.`
|
|
184
|
+
}));
|
|
185
|
+
else if (compare === "unparseable") diagnostics.push(Object.freeze({
|
|
186
|
+
kind: "invalid-field-type",
|
|
187
|
+
field: "anthropic-spec",
|
|
188
|
+
severity: "warn",
|
|
189
|
+
message: `'anthropic-spec' must be an ISO-8601 date string (YYYY-MM-DD); got '${String(anthropicSpec.value)}'.`
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
if (runtimeCompat.value !== void 0 && typeof runtimeCompat.value === "string" && options.runtimeVersion !== void 0) {
|
|
193
|
+
if (!isRuntimeCompatSatisfied(runtimeCompat.value, options.runtimeVersion)) diagnostics.push(Object.freeze({
|
|
194
|
+
kind: "invalid-runtime-compat",
|
|
195
|
+
field: "runtime-compat",
|
|
196
|
+
severity: "error",
|
|
197
|
+
message: `Skill declares runtime-compat '${runtimeCompat.value}' which does not match installed Graphorin '${options.runtimeVersion}'.`,
|
|
198
|
+
hint: "Adjust the skill or upgrade Graphorin so the declared range covers the installed version."
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
for (const key of Object.keys(frontmatter)) {
|
|
202
|
+
if (isRecognisedField(key)) continue;
|
|
203
|
+
if (key.startsWith("graphorin-")) {
|
|
204
|
+
if (unknownFieldPolicy === "reject") diagnostics.push(Object.freeze({
|
|
205
|
+
kind: "unknown-field",
|
|
206
|
+
field: key,
|
|
207
|
+
severity: "error",
|
|
208
|
+
message: `Unknown 'graphorin-*' field '${key}' rejected by unknownFieldPolicy='reject'.`
|
|
209
|
+
}));
|
|
210
|
+
else if (unknownFieldPolicy === "warn") diagnostics.push(Object.freeze({
|
|
211
|
+
kind: "unknown-field",
|
|
212
|
+
field: key,
|
|
213
|
+
severity: "warn",
|
|
214
|
+
message: `Unknown 'graphorin-*' field '${key}'. Will be preserved as opaque metadata.`
|
|
215
|
+
}));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (unknownFieldPolicy === "reject") diagnostics.push(Object.freeze({
|
|
219
|
+
kind: "unknown-field",
|
|
220
|
+
field: key,
|
|
221
|
+
severity: "error",
|
|
222
|
+
message: `Unknown frontmatter field '${key}' rejected by unknownFieldPolicy='reject'.`
|
|
223
|
+
}));
|
|
224
|
+
else if (unknownFieldPolicy === "warn") diagnostics.push(Object.freeze({
|
|
225
|
+
kind: "unknown-field",
|
|
226
|
+
field: key,
|
|
227
|
+
severity: "warn",
|
|
228
|
+
message: `Unknown frontmatter field '${key}'. Will be preserved as opaque metadata.`
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
return Object.freeze({
|
|
232
|
+
raw: Object.freeze({ ...frontmatter }),
|
|
233
|
+
diagnostics: Object.freeze(diagnostics),
|
|
234
|
+
resolved: Object.freeze({
|
|
235
|
+
name,
|
|
236
|
+
description,
|
|
237
|
+
license,
|
|
238
|
+
compatibility,
|
|
239
|
+
metadata,
|
|
240
|
+
allowedTools,
|
|
241
|
+
disableModelInvocation,
|
|
242
|
+
trustLevel,
|
|
243
|
+
runtimeCompat,
|
|
244
|
+
sensitivity,
|
|
245
|
+
sensitivityDefaults,
|
|
246
|
+
sandbox,
|
|
247
|
+
handoffInputFilter,
|
|
248
|
+
anthropicSpec,
|
|
249
|
+
version,
|
|
250
|
+
tools
|
|
251
|
+
})
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Parse the `allowed-tools` field. Accepts either a string (with
|
|
256
|
+
* whitespace-separated entries) or a string array. Returns `null` for
|
|
257
|
+
* unsupported shapes so the validator can attach a typed diagnostic.
|
|
258
|
+
*
|
|
259
|
+
* @stable
|
|
260
|
+
*/
|
|
261
|
+
function parseAllowedToolsValue(value) {
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
if (!value.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return null;
|
|
264
|
+
return Object.freeze(value.map((entry) => entry.trim()));
|
|
265
|
+
}
|
|
266
|
+
if (typeof value === "string") {
|
|
267
|
+
const tokens = value.split(/\s+/u).map((tok) => tok.trim()).filter((tok) => tok.length > 0);
|
|
268
|
+
if (tokens.length === 0) return null;
|
|
269
|
+
return Object.freeze(tokens);
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Parse the `handoff-input-filter` field into a structured
|
|
275
|
+
* declaration. Returns `null` for unsupported shapes; callers should
|
|
276
|
+
* attach a diagnostic when the return value is `null` and the source
|
|
277
|
+
* value was non-undefined.
|
|
278
|
+
*
|
|
279
|
+
* @stable
|
|
280
|
+
*/
|
|
281
|
+
function parseHandoffInputFilter(value) {
|
|
282
|
+
if (typeof value === "string") {
|
|
283
|
+
const trimmed = value.trim();
|
|
284
|
+
if (trimmed === "lastUser") return Object.freeze({ kind: "lastUser" });
|
|
285
|
+
if (trimmed === "summary") return Object.freeze({ kind: "summary" });
|
|
286
|
+
if (trimmed === "full") return Object.freeze({ kind: "full" });
|
|
287
|
+
const lastN = /^lastN-(\d+)$/u.exec(trimmed);
|
|
288
|
+
if (lastN !== null) {
|
|
289
|
+
const n = Number.parseInt(lastN[1] ?? "0", 10);
|
|
290
|
+
if (Number.isFinite(n) && n > 0) return Object.freeze({
|
|
291
|
+
kind: "lastN",
|
|
292
|
+
n
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
298
|
+
const obj = value;
|
|
299
|
+
if (Array.isArray(obj.compose)) {
|
|
300
|
+
const steps = [];
|
|
301
|
+
for (const raw of obj.compose) {
|
|
302
|
+
const step = parseHandoffInputFilterStep(raw);
|
|
303
|
+
if (step === null) return null;
|
|
304
|
+
steps.push(step);
|
|
305
|
+
}
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
kind: "compose",
|
|
308
|
+
steps: Object.freeze(steps)
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function parseHandoffInputFilterStep(raw) {
|
|
315
|
+
if (typeof raw === "string") {
|
|
316
|
+
if (raw === "lastUser") return Object.freeze({ kind: "lastUser" });
|
|
317
|
+
if (raw === "summary") return Object.freeze({ kind: "summary" });
|
|
318
|
+
if (raw === "stripReasoning") return Object.freeze({ kind: "stripReasoning" });
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
322
|
+
const obj = raw;
|
|
323
|
+
if (typeof obj.lastN === "number" && Number.isFinite(obj.lastN) && obj.lastN > 0) return Object.freeze({
|
|
324
|
+
kind: "lastN",
|
|
325
|
+
n: obj.lastN
|
|
326
|
+
});
|
|
327
|
+
if (obj.stripSensitiveOutputs !== void 0 && typeof obj.stripSensitiveOutputs === "object" && obj.stripSensitiveOutputs !== null) {
|
|
328
|
+
const inner = obj.stripSensitiveOutputs;
|
|
329
|
+
const keepTier = typeof inner.keepTier === "string" ? inner.keepTier : void 0;
|
|
330
|
+
return Object.freeze({
|
|
331
|
+
kind: "stripSensitiveOutputs",
|
|
332
|
+
...keepTier === void 0 ? {} : { keepTier }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (typeof obj.lastUser === "boolean") return Object.freeze({ kind: "lastUser" });
|
|
336
|
+
if (typeof obj.summary === "boolean") return Object.freeze({ kind: "summary" });
|
|
337
|
+
if (typeof obj.stripReasoning === "boolean") return Object.freeze({ kind: "stripReasoning" });
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Parse the `tools` field. Accepts either an array of strings (tool
|
|
343
|
+
* names — the loader resolves modules through naming convention) or
|
|
344
|
+
* an array of objects with `name`, `module`, `description`, `tags`.
|
|
345
|
+
* Returns `null` for unsupported shapes.
|
|
346
|
+
*
|
|
347
|
+
* @stable
|
|
348
|
+
*/
|
|
349
|
+
function parseToolsField(value) {
|
|
350
|
+
if (!Array.isArray(value)) return null;
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const entry of value) {
|
|
353
|
+
if (typeof entry === "string") {
|
|
354
|
+
if (entry.trim().length === 0) return null;
|
|
355
|
+
out.push(Object.freeze({ name: entry.trim() }));
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
|
|
359
|
+
const obj = entry;
|
|
360
|
+
if (typeof obj.name !== "string" || obj.name.trim().length === 0) return null;
|
|
361
|
+
const declaration = { name: obj.name.trim() };
|
|
362
|
+
if (typeof obj.module === "string" && obj.module.trim().length > 0) declaration.module = obj.module.trim();
|
|
363
|
+
if (typeof obj.description === "string") declaration.description = obj.description;
|
|
364
|
+
if (Array.isArray(obj.tags) && obj.tags.every((t) => typeof t === "string")) declaration.tags = Object.freeze([...obj.tags]);
|
|
365
|
+
out.push(Object.freeze(declaration));
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return Object.freeze(out);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Best-effort semver-range satisfaction check. Supports the patterns
|
|
374
|
+
* the framework actually emits (`^x.y.z`, `~x.y.z`, `>=x.y.z`,
|
|
375
|
+
* `>x.y.z`, `<=x.y.z`, `<x.y.z`, plain `x.y.z`, the AND combinator
|
|
376
|
+
* with whitespace) without pulling a runtime dependency on `semver`.
|
|
377
|
+
* Unrecognised inputs return `false` so the validator emits a typed
|
|
378
|
+
* diagnostic.
|
|
379
|
+
*
|
|
380
|
+
* @stable
|
|
381
|
+
*/
|
|
382
|
+
function isRuntimeCompatSatisfied(range, version) {
|
|
383
|
+
const versionTuple = parseSemver(version);
|
|
384
|
+
if (versionTuple === null) return false;
|
|
385
|
+
const expressions = range.split(/\s+/u).filter((seg) => seg.length > 0);
|
|
386
|
+
for (const expression of expressions) if (!evaluateSemverExpression(expression, versionTuple)) return false;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
function evaluateSemverExpression(expression, version) {
|
|
390
|
+
if (expression.startsWith("^")) {
|
|
391
|
+
const target$1 = parseSemver(expression.slice(1));
|
|
392
|
+
if (target$1 === null) return false;
|
|
393
|
+
if (target$1[0] === 0 && target$1[1] === 0) return version[0] === 0 && version[1] === 0 && version[2] === target$1[2];
|
|
394
|
+
if (target$1[0] === 0) return version[0] === 0 && version[1] === target$1[1] && cmpTuple(version, target$1) >= 0;
|
|
395
|
+
return version[0] === target$1[0] && cmpTuple(version, target$1) >= 0;
|
|
396
|
+
}
|
|
397
|
+
if (expression.startsWith("~")) {
|
|
398
|
+
const target$1 = parseSemver(expression.slice(1));
|
|
399
|
+
if (target$1 === null) return false;
|
|
400
|
+
return version[0] === target$1[0] && version[1] === target$1[1] && cmpTuple(version, target$1) >= 0;
|
|
401
|
+
}
|
|
402
|
+
if (expression.startsWith(">=")) {
|
|
403
|
+
const target$1 = parseSemver(expression.slice(2));
|
|
404
|
+
return target$1 !== null && cmpTuple(version, target$1) >= 0;
|
|
405
|
+
}
|
|
406
|
+
if (expression.startsWith(">")) {
|
|
407
|
+
const target$1 = parseSemver(expression.slice(1));
|
|
408
|
+
return target$1 !== null && cmpTuple(version, target$1) > 0;
|
|
409
|
+
}
|
|
410
|
+
if (expression.startsWith("<=")) {
|
|
411
|
+
const target$1 = parseSemver(expression.slice(2));
|
|
412
|
+
return target$1 !== null && cmpTuple(version, target$1) <= 0;
|
|
413
|
+
}
|
|
414
|
+
if (expression.startsWith("<")) {
|
|
415
|
+
const target$1 = parseSemver(expression.slice(1));
|
|
416
|
+
return target$1 !== null && cmpTuple(version, target$1) < 0;
|
|
417
|
+
}
|
|
418
|
+
if (expression === "*") return true;
|
|
419
|
+
const target = parseSemver(expression);
|
|
420
|
+
return target !== null && cmpTuple(version, target) === 0;
|
|
421
|
+
}
|
|
422
|
+
function parseSemver(value) {
|
|
423
|
+
const parts = (value.trim().replace(/^v/u, "").split(/[-+]/u, 1)[0] ?? "").split(".");
|
|
424
|
+
if (parts.length < 1 || parts.length > 3) return null;
|
|
425
|
+
const [major, minor, patch] = [
|
|
426
|
+
parts[0] ?? "0",
|
|
427
|
+
parts[1] ?? "0",
|
|
428
|
+
parts[2] ?? "0"
|
|
429
|
+
];
|
|
430
|
+
if (!/^\d+$/u.test(major) || !/^\d+$/u.test(minor) || !/^\d+$/u.test(patch)) return null;
|
|
431
|
+
return [
|
|
432
|
+
Number.parseInt(major, 10),
|
|
433
|
+
Number.parseInt(minor, 10),
|
|
434
|
+
Number.parseInt(patch, 10)
|
|
435
|
+
];
|
|
436
|
+
}
|
|
437
|
+
function cmpTuple(a, b) {
|
|
438
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
439
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
440
|
+
return a[2] - b[2];
|
|
441
|
+
}
|
|
442
|
+
function isRecognisedField(field) {
|
|
443
|
+
if (getKnownField(field) !== void 0) return true;
|
|
444
|
+
if (getGraphorinMapping(field) !== void 0) return true;
|
|
445
|
+
if (field === "metadata") return true;
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
export { isRuntimeCompatSatisfied, parseAllowedToolsValue, parseFrontmatterYaml, parseHandoffInputFilter, parseToolsField, resolveSkillField, splitSkillMd, validateFrontmatter };
|
|
451
|
+
//# sourceMappingURL=index.js.map
|