@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +169 -0
  4. package/anthropic-spec-snapshot.json +114 -0
  5. package/dist/activation/index.d.ts +30 -0
  6. package/dist/activation/index.d.ts.map +1 -0
  7. package/dist/activation/index.js +71 -0
  8. package/dist/activation/index.js.map +1 -0
  9. package/dist/errors/index.d.ts +110 -0
  10. package/dist/errors/index.d.ts.map +1 -0
  11. package/dist/errors/index.js +126 -0
  12. package/dist/errors/index.js.map +1 -0
  13. package/dist/frontmatter/index.d.ts +120 -0
  14. package/dist/frontmatter/index.d.ts.map +1 -0
  15. package/dist/frontmatter/index.js +451 -0
  16. package/dist/frontmatter/index.js.map +1 -0
  17. package/dist/index.d.ts +52 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +51 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/loader/index.d.ts +55 -0
  22. package/dist/loader/index.d.ts.map +1 -0
  23. package/dist/loader/index.js +463 -0
  24. package/dist/loader/index.js.map +1 -0
  25. package/dist/migration/index.d.ts +62 -0
  26. package/dist/migration/index.d.ts.map +1 -0
  27. package/dist/migration/index.js +131 -0
  28. package/dist/migration/index.js.map +1 -0
  29. package/dist/registry/bridge.d.ts +47 -0
  30. package/dist/registry/bridge.d.ts.map +1 -0
  31. package/dist/registry/bridge.js +91 -0
  32. package/dist/registry/bridge.js.map +1 -0
  33. package/dist/registry/index.d.ts +140 -0
  34. package/dist/registry/index.d.ts.map +1 -0
  35. package/dist/registry/index.js +193 -0
  36. package/dist/registry/index.js.map +1 -0
  37. package/dist/spec/index.d.ts +92 -0
  38. package/dist/spec/index.d.ts.map +1 -0
  39. package/dist/spec/index.js +105 -0
  40. package/dist/spec/index.js.map +1 -0
  41. package/dist/types/index.d.ts +305 -0
  42. package/dist/types/index.d.ts.map +1 -0
  43. package/package.json +104 -0
@@ -0,0 +1,463 @@
1
+ import { InputFilterRequiredError, SkillFrontmatterConflictError, SkillLoadError, SkillRequiredFieldMissingError, SkillRuntimeCompatError } from "../errors/index.js";
2
+ import { parseAllowedToolsValue, parseFrontmatterYaml, parseHandoffInputFilter, parseToolsField, splitSkillMd, validateFrontmatter } from "../frontmatter/index.js";
3
+ import { extname, join, relative, resolve, sep } from "node:path";
4
+ import { readFile, readdir, stat } from "node:fs/promises";
5
+ import { installSkillFromGit, installSkillFromNpm, resolveTrustPolicy, verifySkillSignature } from "@graphorin/security/supply-chain";
6
+
7
+ //#region src/loader/index.ts
8
+ const SKILL_MANIFEST_FILENAME = "SKILL.md";
9
+ const DEFAULT_MEDIA_TYPES = Object.freeze({
10
+ ".md": "text/markdown; charset=utf-8",
11
+ ".txt": "text/plain; charset=utf-8",
12
+ ".json": "application/json",
13
+ ".yaml": "application/yaml",
14
+ ".yml": "application/yaml",
15
+ ".ts": "text/typescript; charset=utf-8",
16
+ ".js": "text/javascript; charset=utf-8",
17
+ ".mjs": "text/javascript; charset=utf-8",
18
+ ".cjs": "text/javascript; charset=utf-8",
19
+ ".py": "text/x-python; charset=utf-8",
20
+ ".html": "text/html; charset=utf-8",
21
+ ".css": "text/css; charset=utf-8",
22
+ ".png": "image/png",
23
+ ".jpg": "image/jpeg",
24
+ ".jpeg": "image/jpeg",
25
+ ".gif": "image/gif",
26
+ ".svg": "image/svg+xml",
27
+ ".pdf": "application/pdf"
28
+ });
29
+ /**
30
+ * Load a single skill from any supported source. The loader runs the
31
+ * full frontmatter validator and resolves the supply-chain trust
32
+ * policy so the returned {@link Skill} is ready to be inserted into a
33
+ * `SkillRegistry`.
34
+ *
35
+ * @stable
36
+ */
37
+ async function loadSkillFromSource(source, options = {}) {
38
+ switch (source.kind) {
39
+ case "folder": return loadFromFolder(source.path, source, options);
40
+ case "inline": return loadFromInline(source, options);
41
+ case "npm-package": {
42
+ const installArgs = { packageName: source.packageName };
43
+ if (source.version !== void 0) installArgs.version = source.version;
44
+ if (source.trustLevel !== void 0) installArgs.trustLevel = source.trustLevel;
45
+ if (options.supplyChainPolicy !== void 0) installArgs.policy = options.supplyChainPolicy;
46
+ if (options.signal !== void 0) installArgs.signal = options.signal;
47
+ const status = await installSkillFromNpm(installArgs);
48
+ const installPath = status.installPath;
49
+ if (installPath === void 0) throw new SkillLoadError(source.packageName, "npm install completed without an install path; cannot continue.");
50
+ return loadFromFolder(await locateSkillRoot(installPath, source.packageName), source, options, status.signature);
51
+ }
52
+ case "git-repo": {
53
+ const installArgs = { repoUrl: source.url };
54
+ if (source.ref !== void 0) installArgs.ref = source.ref;
55
+ if (source.trustLevel !== void 0) installArgs.trustLevel = source.trustLevel;
56
+ if (options.supplyChainPolicy !== void 0) installArgs.policy = options.supplyChainPolicy;
57
+ if (options.signal !== void 0) installArgs.signal = options.signal;
58
+ const status = await installSkillFromGit(installArgs);
59
+ const installPath = status.installPath;
60
+ if (installPath === void 0) throw new SkillLoadError(source.url, "git clone completed without a clone path; cannot continue.");
61
+ return loadFromFolder(await locateSkillRoot(installPath, source.url), source, options, status.signature);
62
+ }
63
+ default: throw new SkillLoadError("<unknown>", "unsupported skill source.");
64
+ }
65
+ }
66
+ /**
67
+ * Load multiple skills concurrently. The sources are loaded in parallel and
68
+ * the returned array preserves input order. When `throwOnSourceError === false`
69
+ * (default) a failing source is logged and skipped; otherwise the first
70
+ * rejection propagates out unchanged.
71
+ *
72
+ * @stable
73
+ */
74
+ async function loadSkills(sources, options = {}) {
75
+ const { throwOnSourceError = false, ...inner } = options;
76
+ const loaded = await Promise.all(sources.map(async (source) => {
77
+ try {
78
+ return await loadSkillFromSource(source, inner);
79
+ } catch (err) {
80
+ if (throwOnSourceError) throw err;
81
+ console.warn(`[graphorin/skills] Failed to load source ${describeSource(source)}: ${err.message}`);
82
+ return null;
83
+ }
84
+ }));
85
+ return Object.freeze(loaded.filter((skill) => skill !== null));
86
+ }
87
+ async function loadFromInline(source, options) {
88
+ const trustPolicy = resolveTrustPolicy({
89
+ kind: "folder",
90
+ path: source.skill.basePath ?? "<inline>"
91
+ }, "trusted");
92
+ const { metadata, diagnostics, body } = parseAndValidate(source.skill.skillMd, options);
93
+ const resources = (source.skill.resources ?? []).map((entry) => {
94
+ const relativePath = entry.path;
95
+ const path = source.skill.basePath !== void 0 ? join(source.skill.basePath, entry.path) : entry.path;
96
+ const mediaType = options.mediaTypeFor?.(entry.path) ?? guessMediaType(entry.path);
97
+ return Object.freeze({
98
+ path,
99
+ relativePath,
100
+ ...mediaType === void 0 ? {} : { mediaType },
101
+ async read() {
102
+ return new TextEncoder().encode(entry.content);
103
+ },
104
+ async readText() {
105
+ return entry.content;
106
+ }
107
+ });
108
+ });
109
+ return buildSkill({
110
+ metadata,
111
+ diagnostics,
112
+ body,
113
+ source,
114
+ trustPolicy,
115
+ ...source.skill.tools === void 0 ? {} : { tools: source.skill.tools },
116
+ bodyLoader: () => Promise.resolve(body),
117
+ resourceLoader: () => Promise.resolve(resources)
118
+ });
119
+ }
120
+ async function loadFromFolder(folderPath, source, options, precomputedSignature) {
121
+ const absolutePath = resolve(folderPath);
122
+ let stats;
123
+ try {
124
+ stats = await stat(absolutePath);
125
+ } catch (err) {
126
+ throw new SkillLoadError(describeSource(source), `Could not stat skill directory '${absolutePath}'.`, { cause: err });
127
+ }
128
+ if (!stats.isDirectory()) throw new SkillLoadError(describeSource(source), `Skill source must be a directory; '${absolutePath}' is not.`);
129
+ const manifestPath = join(absolutePath, SKILL_MANIFEST_FILENAME);
130
+ let skillMd;
131
+ try {
132
+ skillMd = await readFile(manifestPath, "utf8");
133
+ } catch (err) {
134
+ throw new SkillLoadError(describeSource(source), `SKILL.md is missing at '${manifestPath}'.`, {
135
+ hint: "Add a SKILL.md file with a YAML frontmatter block.",
136
+ cause: err
137
+ });
138
+ }
139
+ const parsed = parseAndValidate(skillMd, options);
140
+ const { diagnostics, body } = parsed;
141
+ const effectiveTrust = extractTrustLevel(source) ?? (source.kind === "folder" ? capSelfDeclaredTrust(parsed.metadata.graphorinTrustLevel) : parsed.metadata.graphorinTrustLevel);
142
+ const metadata = effectiveTrust === parsed.metadata.graphorinTrustLevel ? parsed.metadata : Object.freeze({
143
+ ...parsed.metadata,
144
+ graphorinTrustLevel: effectiveTrust
145
+ });
146
+ let signature = precomputedSignature;
147
+ if (signature === void 0 && metadata.graphorinSignaturePresent) try {
148
+ signature = await verifySkillSignature({
149
+ skillMd,
150
+ ...options.signal === void 0 ? {} : { signal: options.signal }
151
+ });
152
+ } catch (err) {
153
+ console.warn(`[graphorin/skills] Signature verification of '${metadata.name}' failed: ${err.message}`);
154
+ }
155
+ return buildSkill({
156
+ metadata,
157
+ diagnostics,
158
+ body,
159
+ source,
160
+ trustPolicy: resolveTrustPolicy(source.kind === "folder" ? {
161
+ kind: "folder",
162
+ path: absolutePath
163
+ } : source.kind === "npm-package" ? source.version === void 0 ? {
164
+ kind: "npm-package",
165
+ packageName: source.packageName
166
+ } : {
167
+ kind: "npm-package",
168
+ packageName: source.packageName,
169
+ version: source.version
170
+ } : source.kind === "git-repo" ? source.ref === void 0 ? {
171
+ kind: "git-repo",
172
+ url: source.url
173
+ } : {
174
+ kind: "git-repo",
175
+ url: source.url,
176
+ ref: source.ref
177
+ } : {
178
+ kind: "folder",
179
+ path: absolutePath
180
+ }, coerceForSupplyChain(metadata.graphorinTrustLevel)),
181
+ basePath: absolutePath,
182
+ ...signature === void 0 ? {} : { signature },
183
+ bodyLoader: () => Promise.resolve(body),
184
+ resourceLoader: async (signal) => listResources(absolutePath, options, signal)
185
+ });
186
+ }
187
+ async function listResources(rootPath, options, signal) {
188
+ const out = [];
189
+ for await (const file of walk(rootPath, signal)) {
190
+ const relativePath = relative(rootPath, file);
191
+ if (relativePath === SKILL_MANIFEST_FILENAME) continue;
192
+ const mediaType = options.mediaTypeFor?.(relativePath) ?? guessMediaType(relativePath);
193
+ out.push(Object.freeze({
194
+ path: file,
195
+ relativePath: relativePath.split(sep).join("/"),
196
+ ...mediaType === void 0 ? {} : { mediaType },
197
+ async read(innerSignal) {
198
+ const buffer = await readFile(file, { ...innerSignal === void 0 ? {} : { signal: innerSignal } });
199
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
200
+ },
201
+ async readText(innerSignal) {
202
+ return readFile(file, {
203
+ encoding: "utf8",
204
+ ...innerSignal === void 0 ? {} : { signal: innerSignal }
205
+ });
206
+ }
207
+ }));
208
+ }
209
+ return Object.freeze(out);
210
+ }
211
+ async function* walk(root, signal) {
212
+ const entries = await readdir(root, { withFileTypes: true });
213
+ for (const entry of entries) {
214
+ if (signal?.aborted === true) return;
215
+ const full = join(root, entry.name);
216
+ if (entry.isDirectory()) {
217
+ yield* walk(full, signal);
218
+ continue;
219
+ }
220
+ if (entry.isFile()) yield full;
221
+ }
222
+ }
223
+ async function locateSkillRoot(installPath, sourceLabel) {
224
+ const direct = join(installPath, SKILL_MANIFEST_FILENAME);
225
+ try {
226
+ await stat(direct);
227
+ return installPath;
228
+ } catch {}
229
+ const nodeModulesRoot = join(installPath, "node_modules", sourceLabel);
230
+ try {
231
+ await stat(join(nodeModulesRoot, SKILL_MANIFEST_FILENAME));
232
+ return nodeModulesRoot;
233
+ } catch {}
234
+ const entries = await readdir(installPath, { withFileTypes: true });
235
+ for (const entry of entries) if (entry.isDirectory()) {
236
+ const child = join(installPath, entry.name, SKILL_MANIFEST_FILENAME);
237
+ try {
238
+ await stat(child);
239
+ return join(installPath, entry.name);
240
+ } catch {}
241
+ }
242
+ throw new SkillLoadError(sourceLabel, `Could not locate SKILL.md inside '${installPath}'. Expected at the top-level or one folder deep.`);
243
+ }
244
+ function parseAndValidate(skillMd, options) {
245
+ const split = splitSkillMd(skillMd);
246
+ const frontmatter = parseFrontmatterYaml(split.frontmatter);
247
+ const validateOptions = {};
248
+ if (options.conflictPolicy !== void 0) validateOptions.conflictPolicy = options.conflictPolicy;
249
+ if (options.unknownFieldPolicy !== void 0) validateOptions.unknownFieldPolicy = options.unknownFieldPolicy;
250
+ if (options.runtimeVersion !== void 0) validateOptions.runtimeVersion = options.runtimeVersion;
251
+ const validated = validateFrontmatter(frontmatter, validateOptions);
252
+ for (const diag of validated.diagnostics) if (diag.severity === "error" && diag.kind === "missing-required-field") throw new SkillRequiredFieldMissingError(diag.field, { ...diag.hint === void 0 ? {} : { hint: diag.hint } });
253
+ const metadata = buildMetadata(validated, frontmatter);
254
+ const diagnostics = [...validated.diagnostics];
255
+ appendUntrustedHandoffDiagnostic(metadata, diagnostics, options.conflictPolicy);
256
+ appendInvalidFieldTypeDiagnostics(frontmatter, validated, diagnostics);
257
+ enforceErrorPolicy(metadata, validated, diagnostics, options);
258
+ return Object.freeze({
259
+ metadata,
260
+ diagnostics: Object.freeze(diagnostics),
261
+ body: split.body
262
+ });
263
+ }
264
+ /**
265
+ * RP-11(d): a frontmatter field that is present but unparseable must not be
266
+ * silently dropped (`?? []`). Surface an `invalid-field-type` diagnostic so
267
+ * callers can audit the rejected value, as the parser contracts mandate.
268
+ */
269
+ function appendInvalidFieldTypeDiagnostics(raw, validated, diagnostics) {
270
+ const handoffRaw = validated.resolved.handoffInputFilter.value;
271
+ if (handoffRaw !== void 0 && parseHandoffInputFilter(handoffRaw) === null) diagnostics.push(Object.freeze({
272
+ kind: "invalid-field-type",
273
+ field: "handoff-input-filter",
274
+ severity: "warn",
275
+ message: "'handoff-input-filter' has an unsupported shape; the declaration was ignored.",
276
+ hint: "Use 'lastUser', 'lastN: <n>', 'none', or 'full'."
277
+ }));
278
+ const toolsRaw = raw["graphorin-tools"];
279
+ if (toolsRaw !== void 0 && parseToolsField(toolsRaw) === null) diagnostics.push(Object.freeze({
280
+ kind: "invalid-field-type",
281
+ field: "graphorin-tools",
282
+ severity: "warn",
283
+ message: "'graphorin-tools' must be a list of tool declarations; the value was ignored.",
284
+ hint: "Provide a YAML list, e.g. `graphorin-tools:` with `- name: read_file` entries."
285
+ }));
286
+ }
287
+ /**
288
+ * RP-11(b): under `conflictPolicy: 'error'`, an error-severity diagnostic
289
+ * fails the load through the matching typed exception instead of being
290
+ * silently surfaced. Default (`'warn'`) keeps these as diagnostics.
291
+ */
292
+ function enforceErrorPolicy(metadata, validated, diagnostics, options) {
293
+ if (options.conflictPolicy !== "error") return;
294
+ for (const diag of diagnostics) {
295
+ if (diag.severity !== "error") continue;
296
+ if (diag.kind === "invalid-runtime-compat") {
297
+ const declared = validated.resolved.runtimeCompat.value;
298
+ throw new SkillRuntimeCompatError(metadata.name, typeof declared === "string" ? declared : String(declared), options.runtimeVersion ?? "<unknown>", diag.hint === void 0 ? void 0 : { hint: diag.hint });
299
+ }
300
+ if (diag.kind === "conflict") throw new SkillFrontmatterConflictError(metadata.name, diag.field, [diag.field], diag.hint === void 0 ? void 0 : { hint: diag.hint });
301
+ }
302
+ }
303
+ function buildMetadata(validated, raw) {
304
+ const name = String(validated.resolved.name.value ?? "").trim();
305
+ const description = String(validated.resolved.description.value ?? "").trim();
306
+ const allowedToolsRaw = validated.resolved.allowedTools.value;
307
+ const allowedTools = allowedToolsRaw === void 0 ? void 0 : parseAllowedToolsValue(allowedToolsRaw);
308
+ const handoffFilter = validated.resolved.handoffInputFilter.value === void 0 ? void 0 : parseHandoffInputFilter(validated.resolved.handoffInputFilter.value);
309
+ const trustLevelRaw = validated.resolved.trustLevel.value;
310
+ const trustLevel = (() => {
311
+ if (trustLevelRaw === "trusted" || trustLevelRaw === "trusted-with-scripts" || trustLevelRaw === "untrusted" || trustLevelRaw === "unknown") return trustLevelRaw;
312
+ return "unknown";
313
+ })();
314
+ const sandbox = validated.resolved.sandbox.value;
315
+ const sensitivity = validated.resolved.sensitivity.value;
316
+ const sensitivityDefaultsRaw = validated.resolved.sensitivityDefaults.value;
317
+ const sensitivityDefaults = sensitivityDefaultsRaw !== null && typeof sensitivityDefaultsRaw === "object" ? Object.freeze({ ...sensitivityDefaultsRaw }) : void 0;
318
+ const metadataObj = validated.resolved.metadata.value;
319
+ const metadataValue = metadataObj !== null && typeof metadataObj === "object" ? Object.freeze({ ...metadataObj }) : void 0;
320
+ const result = {
321
+ name,
322
+ description,
323
+ disableModelInvocation: validated.resolved.disableModelInvocation.value === true,
324
+ graphorinTrustLevel: trustLevel,
325
+ graphorinSignaturePresent: "graphorin-signature" in raw,
326
+ raw: Object.freeze({ ...raw })
327
+ };
328
+ if (typeof validated.resolved.license.value === "string") result.license = validated.resolved.license.value;
329
+ if (typeof validated.resolved.compatibility.value === "string") result.compatibility = validated.resolved.compatibility.value;
330
+ if (metadataValue !== void 0) result.metadata = metadataValue;
331
+ if (allowedTools !== void 0 && allowedTools !== null) result.allowedTools = Object.freeze([...allowedTools]);
332
+ if (typeof validated.resolved.runtimeCompat.value === "string") result.graphorinRuntimeCompat = validated.resolved.runtimeCompat.value;
333
+ if (typeof sensitivity === "string") result.graphorinSensitivity = sensitivity;
334
+ if (sensitivityDefaults !== void 0) result.graphorinSensitivityDefaults = sensitivityDefaults;
335
+ if (sandbox !== null && typeof sandbox === "object") result.graphorinSandbox = Object.freeze({ ...sandbox });
336
+ if (handoffFilter !== void 0 && handoffFilter !== null) result.graphorinHandoffInputFilter = handoffFilter;
337
+ if (typeof validated.resolved.anthropicSpec.value === "string") result.graphorinAnthropicSpec = validated.resolved.anthropicSpec.value;
338
+ if (typeof validated.resolved.version.value === "string") result.graphorinVersion = validated.resolved.version.value;
339
+ return Object.freeze(result);
340
+ }
341
+ function appendUntrustedHandoffDiagnostic(metadata, diagnostics, conflictPolicy = "warn") {
342
+ if (metadata.graphorinTrustLevel === "untrusted") {
343
+ if (metadata.graphorinHandoffInputFilter?.kind === "full") diagnostics.push(Object.freeze({
344
+ kind: "untrusted-handoff-filter-required",
345
+ field: "graphorin-handoff-input-filter",
346
+ severity: "warn",
347
+ message: `Untrusted skill '${metadata.name}' declared 'graphorin-handoff-input-filter: full'. The full-message filter is rejected for untrusted skills; the runtime will fall back to 'lastUser'.`,
348
+ hint: "Replace 'full' with 'lastUser' (or another bounded filter) to silence this diagnostic."
349
+ }));
350
+ else if (metadata.graphorinHandoffInputFilter === void 0) diagnostics.push(Object.freeze({
351
+ kind: "untrusted-handoff-filter-required",
352
+ field: "graphorin-handoff-input-filter",
353
+ severity: conflictPolicy === "error" ? "error" : "warn",
354
+ message: `Untrusted skill '${metadata.name}' did not declare 'graphorin-handoff-input-filter'. The runtime will throw if the skill invokes Agent.toTool()/handoff().`,
355
+ hint: "Add 'graphorin-handoff-input-filter: lastUser' to the SKILL.md frontmatter."
356
+ }));
357
+ }
358
+ }
359
+ function buildSkill(args) {
360
+ let bodyCache = null;
361
+ let resourcesCache = null;
362
+ let bodyPromise = null;
363
+ let resourcesPromise = null;
364
+ const toolDeclarations = parseToolsField(args.metadata.raw["graphorin-tools"]) ?? [];
365
+ const tools = Object.freeze([...args.tools ?? []]);
366
+ return Object.freeze({
367
+ metadata: args.metadata,
368
+ source: args.source,
369
+ ...args.basePath === void 0 ? {} : { basePath: args.basePath },
370
+ trustPolicy: args.trustPolicy,
371
+ ...args.signature === void 0 ? {} : { signature: args.signature },
372
+ async body(_signal) {
373
+ if (bodyCache !== null) return bodyCache;
374
+ if (bodyPromise === null) bodyPromise = args.bodyLoader().then((value) => {
375
+ bodyCache = value;
376
+ return value;
377
+ });
378
+ return bodyPromise;
379
+ },
380
+ async resources(signal) {
381
+ if (resourcesCache !== null) return resourcesCache;
382
+ if (resourcesPromise === null) resourcesPromise = args.resourceLoader(signal).then((value) => {
383
+ resourcesCache = value;
384
+ return value;
385
+ });
386
+ return resourcesPromise;
387
+ },
388
+ tools() {
389
+ return tools;
390
+ },
391
+ toolDeclarations() {
392
+ return toolDeclarations;
393
+ },
394
+ diagnostics() {
395
+ return args.diagnostics;
396
+ }
397
+ });
398
+ }
399
+ function describeSource(source) {
400
+ switch (source.kind) {
401
+ case "folder": return `folder:${source.path}`;
402
+ case "npm-package": return source.version === void 0 ? `npm:${source.packageName}` : `npm:${source.packageName}@${source.version}`;
403
+ case "git-repo": return source.ref === void 0 ? `git:${source.url}` : `git:${source.url}@${source.ref}`;
404
+ case "inline": return "inline";
405
+ default: return "unknown";
406
+ }
407
+ }
408
+ function extractTrustLevel(source) {
409
+ if (source.kind === "npm-package" || source.kind === "git-repo" || source.kind === "folder") return source.trustLevel;
410
+ }
411
+ /**
412
+ * Cap a folder skill's self-declared trust level. A directory on disk —
413
+ * possibly downloaded from the internet — cannot self-promote to a trusted
414
+ * tier without an operator override (RP-9): `trusted` /
415
+ * `trusted-with-scripts` collapse to `'unknown'` (sandbox forced, signature
416
+ * optional, outputs taint-marked), while `untrusted` / `unknown` pass
417
+ * through unchanged.
418
+ */
419
+ function capSelfDeclaredTrust(level) {
420
+ return level === "trusted" || level === "trusted-with-scripts" ? "unknown" : level;
421
+ }
422
+ /**
423
+ * Translate the loader's four-valued trust level into the three-
424
+ * valued trust level the supply-chain installer expects.
425
+ *
426
+ * The 'unknown' value collapses to 'untrusted' so the supply-chain
427
+ * resolver picks the strict signature + `--ignore-scripts` policy.
428
+ * The loader keeps the original 'unknown' on the skill's metadata
429
+ * record so the runtime sandbox tier resolver continues to apply
430
+ * the correct policy.
431
+ */
432
+ function coerceForSupplyChain(level) {
433
+ if (level === "unknown") return "untrusted";
434
+ return level;
435
+ }
436
+ function guessMediaType(path) {
437
+ return DEFAULT_MEDIA_TYPES[extname(path).toLowerCase()];
438
+ }
439
+ /**
440
+ * Required handoff-filter declaration helper. Returns the typed
441
+ * declaration the loader parsed from frontmatter; throws
442
+ * {@link InputFilterRequiredError} when the skill is untrusted and the
443
+ * field is missing. Used by the agent runtime in Phase 12 right
444
+ * before instantiating an untrusted skill's sub-agent.
445
+ *
446
+ * @stable
447
+ */
448
+ function requireHandoffInputFilter(metadata) {
449
+ if (metadata.graphorinHandoffInputFilter !== void 0) {
450
+ if (metadata.graphorinTrustLevel === "untrusted" && metadata.graphorinHandoffInputFilter.kind === "full") return Object.freeze({ kind: "lastUser" });
451
+ return metadata.graphorinHandoffInputFilter;
452
+ }
453
+ if (metadata.graphorinTrustLevel === "untrusted") throw new InputFilterRequiredError(metadata.name);
454
+ if (metadata.graphorinTrustLevel === "unknown") throw new InputFilterRequiredError(metadata.name);
455
+ return Object.freeze({
456
+ kind: "lastN",
457
+ n: 10
458
+ });
459
+ }
460
+
461
+ //#endregion
462
+ export { loadSkillFromSource, loadSkills, requireHandoffInputFilter };
463
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>>","installArgs: Parameters<typeof installSkillFromNpm>[0]","installArgs: Parameters<typeof installSkillFromGit>[0]","resources: SkillResource[]","stats: Stats","skillMd: string","effectiveTrust: SkillsTrustLevel","metadata: SkillMetadata","signature: SkillSignatureVerificationResult | undefined","out: SkillResource[]","validateOptions: Parameters<typeof validateFrontmatter>[1]","diagnostics: FrontmatterDiagnostic[]","trustLevel: SkillsTrustLevel","result: Mutable<SkillMetadata>","bodyCache: string | null","resourcesCache: ReadonlyArray<SkillResource> | null","bodyPromise: Promise<string> | null","resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null"],"sources":["../../src/loader/index.ts"],"sourcesContent":["/**\n * Skill loader.\n *\n * Implements three-tier progressive disclosure:\n *\n * - **Tier 1** (always): {@link Skill.metadata} — parsed at load\n * time from the SKILL.md frontmatter.\n * - **Tier 2** (on activation): {@link Skill.body} — the loader\n * reads the markdown body lazily; subsequent calls return the\n * cached value.\n * - **Tier 3** (on demand): {@link Skill.resources} — the loader\n * walks the skill directory lazily; resource bytes are only read\n * when {@link SkillResource.read} is invoked.\n *\n * The loader supports four sources:\n *\n * - `{ kind: 'folder', path }` — read SKILL.md from disk.\n * - `{ kind: 'npm-package', ... }` — install via the supply-chain\n * helper from `@graphorin/security/supply-chain`, then read.\n * - `{ kind: 'git-repo', ... }` — shallow-clone via the\n * supply-chain helper, then read.\n * - `{ kind: 'inline', skill: ... }` — caller supplies the parsed\n * payload; the loader only validates the frontmatter. Useful for\n * tests and bundled defaults.\n *\n * @packageDocumentation\n */\n\nimport type { Stats } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join, relative, resolve, sep } from 'node:path';\n\nimport {\n installSkillFromGit,\n installSkillFromNpm,\n type ResolvedSkillTrustPolicy,\n resolveTrustPolicy,\n type SkillSignatureVerificationResult,\n type SupplyChainPolicy,\n verifySkillSignature,\n} from '@graphorin/security/supply-chain';\n\nimport {\n InputFilterRequiredError,\n SkillFrontmatterConflictError,\n SkillLoadError,\n SkillRequiredFieldMissingError,\n SkillRuntimeCompatError,\n} from '../errors/index.js';\nimport {\n parseAllowedToolsValue,\n parseFrontmatterYaml,\n parseHandoffInputFilter,\n parseToolsField,\n splitSkillMd,\n type ValidatedFrontmatter,\n validateFrontmatter,\n} from '../frontmatter/index.js';\nimport type {\n FrontmatterDiagnostic,\n FrontmatterValidatorPolicy,\n HandoffInputFilterDeclaration,\n InlineSkillTool,\n Skill,\n SkillMetadata,\n SkillResource,\n SkillSource,\n SkillsTrustLevel,\n SkillToolDeclaration,\n UnknownFieldPolicy,\n} from '../types/index.js';\n\n/** Options forwarded to {@link loadSkillFromSource}. */\nexport interface LoadSkillOptions {\n readonly conflictPolicy?: FrontmatterValidatorPolicy;\n readonly unknownFieldPolicy?: UnknownFieldPolicy;\n readonly runtimeVersion?: string;\n readonly supplyChainPolicy?: SupplyChainPolicy;\n readonly signal?: AbortSignal;\n /** Override the bundled MIME-type guesser for resource files. */\n readonly mediaTypeFor?: (path: string) => string | undefined;\n}\n\n/** Aggregate options accepted by {@link loadSkills}. */\nexport interface LoadSkillsOptions extends LoadSkillOptions {\n /**\n * Fail fast if any source produces a {@link SkillLoadError}. When\n * `false` (default) the loader logs the source path on the\n * diagnostic and continues with the next source.\n */\n readonly throwOnSourceError?: boolean;\n}\n\nconst SKILL_MANIFEST_FILENAME = 'SKILL.md';\n\nconst DEFAULT_MEDIA_TYPES: Readonly<Record<string, string>> = Object.freeze({\n '.md': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.json': 'application/json',\n '.yaml': 'application/yaml',\n '.yml': 'application/yaml',\n '.ts': 'text/typescript; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.cjs': 'text/javascript; charset=utf-8',\n '.py': 'text/x-python; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.pdf': 'application/pdf',\n});\n\n/**\n * Load a single skill from any supported source. The loader runs the\n * full frontmatter validator and resolves the supply-chain trust\n * policy so the returned {@link Skill} is ready to be inserted into a\n * `SkillRegistry`.\n *\n * @stable\n */\nexport async function loadSkillFromSource(\n source: SkillSource,\n options: LoadSkillOptions = {},\n): Promise<Skill> {\n switch (source.kind) {\n case 'folder':\n return loadFromFolder(source.path, source, options);\n case 'inline':\n return loadFromInline(source, options);\n case 'npm-package': {\n const installArgs: Parameters<typeof installSkillFromNpm>[0] = {\n packageName: source.packageName,\n };\n if (source.version !== undefined)\n (installArgs as Mutable<typeof installArgs>).version = source.version;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromNpm(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.packageName,\n 'npm install completed without an install path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.packageName);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n case 'git-repo': {\n const installArgs: Parameters<typeof installSkillFromGit>[0] = {\n repoUrl: source.url,\n };\n if (source.ref !== undefined) (installArgs as Mutable<typeof installArgs>).ref = source.ref;\n if (source.trustLevel !== undefined)\n (installArgs as Mutable<typeof installArgs>).trustLevel = source.trustLevel;\n if (options.supplyChainPolicy !== undefined)\n (installArgs as Mutable<typeof installArgs>).policy = options.supplyChainPolicy;\n if (options.signal !== undefined)\n (installArgs as Mutable<typeof installArgs>).signal = options.signal;\n const status = await installSkillFromGit(installArgs);\n const installPath = status.installPath;\n if (installPath === undefined) {\n throw new SkillLoadError(\n source.url,\n 'git clone completed without a clone path; cannot continue.',\n );\n }\n const resolved = await locateSkillRoot(installPath, source.url);\n return loadFromFolder(resolved, source, options, status.signature);\n }\n default: {\n const exhaustive: never = source;\n void exhaustive;\n throw new SkillLoadError('<unknown>', 'unsupported skill source.');\n }\n }\n}\n\n/**\n * Load multiple skills concurrently. The sources are loaded in parallel and\n * the returned array preserves input order. When `throwOnSourceError === false`\n * (default) a failing source is logged and skipped; otherwise the first\n * rejection propagates out unchanged.\n *\n * @stable\n */\nexport async function loadSkills(\n sources: ReadonlyArray<SkillSource>,\n options: LoadSkillsOptions = {},\n): Promise<ReadonlyArray<Skill>> {\n const { throwOnSourceError = false, ...inner } = options;\n const loaded = await Promise.all(\n sources.map(async (source): Promise<Skill | null> => {\n try {\n return await loadSkillFromSource(source, inner);\n } catch (err) {\n if (throwOnSourceError) throw err;\n // We cannot create a Skill from a failed source, but we want to\n // preserve a structured diagnostic so callers can audit which\n // sources failed without re-running the loader.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Failed to load source ${describeSource(source)}: ${(err as Error).message}`,\n );\n return null;\n }\n }),\n );\n return Object.freeze(loaded.filter((skill): skill is Skill => skill !== null));\n}\n\nasync function loadFromInline(\n source: Extract<SkillSource, { kind: 'inline' }>,\n options: LoadSkillOptions,\n): Promise<Skill> {\n const trustPolicy = resolveTrustPolicy(\n { kind: 'folder', path: source.skill.basePath ?? '<inline>' },\n 'trusted',\n );\n const { metadata, diagnostics, body } = parseAndValidate(source.skill.skillMd, options);\n const resourceList = source.skill.resources ?? [];\n const resources: SkillResource[] = resourceList.map((entry) => {\n const relativePath = entry.path;\n const path =\n source.skill.basePath !== undefined ? join(source.skill.basePath, entry.path) : entry.path;\n const mediaType = options.mediaTypeFor?.(entry.path) ?? guessMediaType(entry.path);\n return Object.freeze({\n path,\n relativePath,\n ...(mediaType === undefined ? {} : { mediaType }),\n async read() {\n return new TextEncoder().encode(entry.content);\n },\n async readText() {\n return entry.content;\n },\n } satisfies SkillResource);\n });\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n ...(source.skill.tools === undefined ? {} : { tools: source.skill.tools }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: () => Promise.resolve(resources),\n });\n}\n\nasync function loadFromFolder(\n folderPath: string,\n source: SkillSource,\n options: LoadSkillOptions,\n precomputedSignature?: SkillSignatureVerificationResult | undefined,\n): Promise<Skill> {\n const absolutePath = resolve(folderPath);\n let stats: Stats;\n try {\n stats = await stat(absolutePath);\n } catch (err) {\n throw new SkillLoadError(\n describeSource(source),\n `Could not stat skill directory '${absolutePath}'.`,\n { cause: err },\n );\n }\n if (!stats.isDirectory()) {\n throw new SkillLoadError(\n describeSource(source),\n `Skill source must be a directory; '${absolutePath}' is not.`,\n );\n }\n const manifestPath = join(absolutePath, SKILL_MANIFEST_FILENAME);\n let skillMd: string;\n try {\n skillMd = await readFile(manifestPath, 'utf8');\n } catch (err) {\n throw new SkillLoadError(describeSource(source), `SKILL.md is missing at '${manifestPath}'.`, {\n hint: 'Add a SKILL.md file with a YAML frontmatter block.',\n cause: err,\n });\n }\n const parsed = parseAndValidate(skillMd, options);\n const { diagnostics, body } = parsed;\n // RP-9: trust is granted by the integrator, never the artifact. An operator\n // override on the source wins; absent one, a folder's self-declared\n // 'trusted'/'trusted-with-scripts' is capped at 'unknown' so a downloaded\n // directory cannot promote itself out of the sandbox + taint-marking. The\n // resolved level is written back onto the metadata so every downstream\n // consumer (tool stamping, sandbox tier, inbound sanitization) sees it.\n const operatorTrust = extractTrustLevel(source);\n const effectiveTrust: SkillsTrustLevel =\n operatorTrust ??\n (source.kind === 'folder'\n ? capSelfDeclaredTrust(parsed.metadata.graphorinTrustLevel)\n : parsed.metadata.graphorinTrustLevel);\n const metadata: SkillMetadata =\n effectiveTrust === parsed.metadata.graphorinTrustLevel\n ? parsed.metadata\n : (Object.freeze({\n ...parsed.metadata,\n graphorinTrustLevel: effectiveTrust,\n }) as SkillMetadata);\n\n let signature: SkillSignatureVerificationResult | undefined = precomputedSignature;\n if (signature === undefined && metadata.graphorinSignaturePresent) {\n try {\n signature = await verifySkillSignature({\n skillMd,\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n });\n } catch (err) {\n // Surface the signature failure as a diagnostic — the supply-\n // chain installer is the one that decides whether to refuse the\n // install based on the resolved trust policy. The folder loader\n // tolerates an unverifiable signature so operators can iterate\n // on local skills before signing.\n // eslint-disable-next-line no-console\n console.warn(\n `[graphorin/skills] Signature verification of '${metadata.name}' failed: ${(err as Error).message}`,\n );\n }\n }\n\n const trustPolicy = resolveTrustPolicy(\n source.kind === 'folder'\n ? { kind: 'folder', path: absolutePath }\n : source.kind === 'npm-package'\n ? source.version === undefined\n ? { kind: 'npm-package', packageName: source.packageName }\n : { kind: 'npm-package', packageName: source.packageName, version: source.version }\n : source.kind === 'git-repo'\n ? source.ref === undefined\n ? { kind: 'git-repo', url: source.url }\n : { kind: 'git-repo', url: source.url, ref: source.ref }\n : { kind: 'folder', path: absolutePath },\n coerceForSupplyChain(metadata.graphorinTrustLevel),\n );\n\n return buildSkill({\n metadata,\n diagnostics,\n body,\n source,\n trustPolicy,\n basePath: absolutePath,\n ...(signature === undefined ? {} : { signature }),\n bodyLoader: () => Promise.resolve(body),\n resourceLoader: async (signal) => listResources(absolutePath, options, signal),\n });\n}\n\nasync function listResources(\n rootPath: string,\n options: LoadSkillOptions,\n signal?: AbortSignal,\n): Promise<ReadonlyArray<SkillResource>> {\n const out: SkillResource[] = [];\n for await (const file of walk(rootPath, signal)) {\n const relativePath = relative(rootPath, file);\n if (relativePath === SKILL_MANIFEST_FILENAME) continue;\n const mediaType = options.mediaTypeFor?.(relativePath) ?? guessMediaType(relativePath);\n out.push(\n Object.freeze({\n path: file,\n relativePath: relativePath.split(sep).join('/'),\n ...(mediaType === undefined ? {} : { mediaType }),\n async read(innerSignal?: AbortSignal): Promise<Uint8Array> {\n const buffer = await readFile(file, {\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n },\n async readText(innerSignal?: AbortSignal): Promise<string> {\n return readFile(file, {\n encoding: 'utf8',\n ...(innerSignal === undefined ? {} : { signal: innerSignal }),\n });\n },\n } satisfies SkillResource),\n );\n }\n return Object.freeze(out);\n}\n\nasync function* walk(root: string, signal?: AbortSignal): AsyncIterable<string> {\n const entries = await readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n if (signal?.aborted === true) return;\n const full = join(root, entry.name);\n if (entry.isDirectory()) {\n yield* walk(full, signal);\n continue;\n }\n if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nasync function locateSkillRoot(installPath: string, sourceLabel: string): Promise<string> {\n const direct = join(installPath, SKILL_MANIFEST_FILENAME);\n try {\n await stat(direct);\n return installPath;\n } catch {\n // continue\n }\n // RP-10: npm packages land under `node_modules/<packageName>`. The label is\n // the package name for npm sources; for git it is a URL and this probe\n // simply misses, falling through to the one-level-deep scan below.\n const nodeModulesRoot = join(installPath, 'node_modules', sourceLabel);\n try {\n await stat(join(nodeModulesRoot, SKILL_MANIFEST_FILENAME));\n return nodeModulesRoot;\n } catch {\n // continue\n }\n const entries = await readdir(installPath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const child = join(installPath, entry.name, SKILL_MANIFEST_FILENAME);\n try {\n await stat(child);\n return join(installPath, entry.name);\n } catch {\n // continue\n }\n }\n }\n throw new SkillLoadError(\n sourceLabel,\n `Could not locate SKILL.md inside '${installPath}'. Expected at the top-level or one folder deep.`,\n );\n}\n\ninterface ParsedAndValidated {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n}\n\nfunction parseAndValidate(skillMd: string, options: LoadSkillOptions): ParsedAndValidated {\n const split = splitSkillMd(skillMd);\n const frontmatter = parseFrontmatterYaml(split.frontmatter);\n const validateOptions: Parameters<typeof validateFrontmatter>[1] = {};\n if (options.conflictPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).conflictPolicy = options.conflictPolicy;\n if (options.unknownFieldPolicy !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).unknownFieldPolicy =\n options.unknownFieldPolicy;\n if (options.runtimeVersion !== undefined)\n (validateOptions as Mutable<typeof validateOptions>).runtimeVersion = options.runtimeVersion;\n const validated = validateFrontmatter(frontmatter, validateOptions);\n for (const diag of validated.diagnostics) {\n if (diag.severity === 'error' && diag.kind === 'missing-required-field') {\n throw new SkillRequiredFieldMissingError(diag.field, {\n ...(diag.hint === undefined ? {} : { hint: diag.hint }),\n });\n }\n }\n const metadata = buildMetadata(validated, frontmatter);\n const diagnostics: FrontmatterDiagnostic[] = [...validated.diagnostics];\n appendUntrustedHandoffDiagnostic(metadata, diagnostics, options.conflictPolicy);\n appendInvalidFieldTypeDiagnostics(frontmatter, validated, diagnostics);\n enforceErrorPolicy(metadata, validated, diagnostics, options);\n return Object.freeze({\n metadata,\n diagnostics: Object.freeze(diagnostics),\n body: split.body,\n });\n}\n\n/**\n * RP-11(d): a frontmatter field that is present but unparseable must not be\n * silently dropped (`?? []`). Surface an `invalid-field-type` diagnostic so\n * callers can audit the rejected value, as the parser contracts mandate.\n */\nfunction appendInvalidFieldTypeDiagnostics(\n raw: Record<string, unknown>,\n validated: ValidatedFrontmatter,\n diagnostics: FrontmatterDiagnostic[],\n): void {\n const handoffRaw = validated.resolved.handoffInputFilter.value;\n if (handoffRaw !== undefined && parseHandoffInputFilter(handoffRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'handoff-input-filter',\n severity: 'warn',\n message: \"'handoff-input-filter' has an unsupported shape; the declaration was ignored.\",\n hint: \"Use 'lastUser', 'lastN: <n>', 'none', or 'full'.\",\n }),\n );\n }\n const toolsRaw = raw['graphorin-tools'];\n if (toolsRaw !== undefined && parseToolsField(toolsRaw) === null) {\n diagnostics.push(\n Object.freeze({\n kind: 'invalid-field-type',\n field: 'graphorin-tools',\n severity: 'warn',\n message: \"'graphorin-tools' must be a list of tool declarations; the value was ignored.\",\n hint: 'Provide a YAML list, e.g. `graphorin-tools:` with `- name: read_file` entries.',\n }),\n );\n }\n}\n\n/**\n * RP-11(b): under `conflictPolicy: 'error'`, an error-severity diagnostic\n * fails the load through the matching typed exception instead of being\n * silently surfaced. Default (`'warn'`) keeps these as diagnostics.\n */\nfunction enforceErrorPolicy(\n metadata: SkillMetadata,\n validated: ValidatedFrontmatter,\n diagnostics: ReadonlyArray<FrontmatterDiagnostic>,\n options: LoadSkillOptions,\n): void {\n if (options.conflictPolicy !== 'error') return;\n for (const diag of diagnostics) {\n if (diag.severity !== 'error') continue;\n if (diag.kind === 'invalid-runtime-compat') {\n const declared = validated.resolved.runtimeCompat.value;\n throw new SkillRuntimeCompatError(\n metadata.name,\n typeof declared === 'string' ? declared : String(declared),\n options.runtimeVersion ?? '<unknown>',\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n if (diag.kind === 'conflict') {\n throw new SkillFrontmatterConflictError(\n metadata.name,\n diag.field,\n [diag.field],\n diag.hint === undefined ? undefined : { hint: diag.hint },\n );\n }\n }\n}\n\nfunction buildMetadata(\n validated: ValidatedFrontmatter,\n raw: Record<string, unknown>,\n): SkillMetadata {\n const name = String(validated.resolved.name.value ?? '').trim();\n const description = String(validated.resolved.description.value ?? '').trim();\n const allowedToolsRaw = validated.resolved.allowedTools.value;\n const allowedTools =\n allowedToolsRaw === undefined ? undefined : parseAllowedToolsValue(allowedToolsRaw);\n const handoffFilter =\n validated.resolved.handoffInputFilter.value === undefined\n ? undefined\n : parseHandoffInputFilter(validated.resolved.handoffInputFilter.value);\n const trustLevelRaw = validated.resolved.trustLevel.value;\n // Phase 08 § \"Frontmatter validator\" recognises four explicit trust\n // levels: 'trusted' | 'trusted-with-scripts' | 'unknown' |\n // 'untrusted'. A skill that did not declare the field is treated as\n // 'unknown' (sandbox forced; signature optional) — that is the\n // default-deny posture per DEC-148 risk mitigation.\n const trustLevel: SkillsTrustLevel = (() => {\n if (\n trustLevelRaw === 'trusted' ||\n trustLevelRaw === 'trusted-with-scripts' ||\n trustLevelRaw === 'untrusted' ||\n trustLevelRaw === 'unknown'\n ) {\n return trustLevelRaw;\n }\n return 'unknown';\n })();\n const sandbox = validated.resolved.sandbox.value;\n const sensitivity = validated.resolved.sensitivity.value;\n const sensitivityDefaultsRaw = validated.resolved.sensitivityDefaults.value;\n const sensitivityDefaults =\n sensitivityDefaultsRaw !== null && typeof sensitivityDefaultsRaw === 'object'\n ? Object.freeze({ ...(sensitivityDefaultsRaw as Record<string, string>) })\n : undefined;\n const metadataObj = validated.resolved.metadata.value;\n const metadataValue =\n metadataObj !== null && typeof metadataObj === 'object'\n ? Object.freeze({ ...(metadataObj as Record<string, unknown>) })\n : undefined;\n const result: Mutable<SkillMetadata> = {\n name,\n description,\n disableModelInvocation: validated.resolved.disableModelInvocation.value === true,\n graphorinTrustLevel: trustLevel,\n graphorinSignaturePresent: 'graphorin-signature' in raw,\n raw: Object.freeze({ ...raw }),\n };\n if (typeof validated.resolved.license.value === 'string')\n result.license = validated.resolved.license.value;\n if (typeof validated.resolved.compatibility.value === 'string')\n result.compatibility = validated.resolved.compatibility.value;\n if (metadataValue !== undefined) result.metadata = metadataValue;\n if (allowedTools !== undefined && allowedTools !== null)\n result.allowedTools = Object.freeze([...allowedTools]);\n if (typeof validated.resolved.runtimeCompat.value === 'string')\n result.graphorinRuntimeCompat = validated.resolved.runtimeCompat.value;\n if (typeof sensitivity === 'string') result.graphorinSensitivity = sensitivity;\n if (sensitivityDefaults !== undefined) result.graphorinSensitivityDefaults = sensitivityDefaults;\n if (sandbox !== null && typeof sandbox === 'object')\n result.graphorinSandbox = Object.freeze({ ...(sandbox as Record<string, unknown>) });\n if (handoffFilter !== undefined && handoffFilter !== null)\n result.graphorinHandoffInputFilter = handoffFilter;\n if (typeof validated.resolved.anthropicSpec.value === 'string')\n result.graphorinAnthropicSpec = validated.resolved.anthropicSpec.value;\n if (typeof validated.resolved.version.value === 'string')\n result.graphorinVersion = validated.resolved.version.value;\n return Object.freeze(result) as SkillMetadata;\n}\n\nfunction appendUntrustedHandoffDiagnostic(\n metadata: SkillMetadata,\n diagnostics: FrontmatterDiagnostic[],\n conflictPolicy: FrontmatterValidatorPolicy = 'warn',\n): void {\n // Untrusted skills that declared `filter: full` are an explicit\n // attempt to exfiltrate the entire conversation through a sub-agent\n // — Phase 08 / ADR-040 mandate that we WARN and ignore. The agent\n // runtime in Phase 12 will refuse the filter regardless of this\n // diagnostic.\n if (metadata.graphorinTrustLevel === 'untrusted') {\n if (metadata.graphorinHandoffInputFilter?.kind === 'full') {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: 'warn',\n message: `Untrusted skill '${metadata.name}' declared 'graphorin-handoff-input-filter: full'. The full-message filter is rejected for untrusted skills; the runtime will fall back to 'lastUser'.`,\n hint: \"Replace 'full' with 'lastUser' (or another bounded filter) to silence this diagnostic.\",\n }),\n );\n } else if (metadata.graphorinHandoffInputFilter === undefined) {\n diagnostics.push(\n Object.freeze({\n kind: 'untrusted-handoff-filter-required',\n field: 'graphorin-handoff-input-filter',\n severity: conflictPolicy === 'error' ? 'error' : 'warn',\n message: `Untrusted skill '${metadata.name}' did not declare 'graphorin-handoff-input-filter'. The runtime will throw if the skill invokes Agent.toTool()/handoff().`,\n hint: \"Add 'graphorin-handoff-input-filter: lastUser' to the SKILL.md frontmatter.\",\n }),\n );\n }\n }\n}\n\ninterface BuildSkillArgs {\n readonly metadata: SkillMetadata;\n readonly diagnostics: ReadonlyArray<FrontmatterDiagnostic>;\n readonly body: string;\n readonly source: SkillSource;\n readonly trustPolicy: ResolvedSkillTrustPolicy;\n readonly basePath?: string;\n readonly signature?: SkillSignatureVerificationResult;\n readonly tools?: ReadonlyArray<InlineSkillTool>;\n readonly bodyLoader: () => Promise<string>;\n readonly resourceLoader: (signal?: AbortSignal) => Promise<ReadonlyArray<SkillResource>>;\n}\n\nfunction buildSkill(args: BuildSkillArgs): Skill {\n let bodyCache: string | null = null;\n let resourcesCache: ReadonlyArray<SkillResource> | null = null;\n let bodyPromise: Promise<string> | null = null;\n let resourcesPromise: Promise<ReadonlyArray<SkillResource>> | null = null;\n const toolDeclarations = parseToolsField(args.metadata.raw['graphorin-tools']) ?? [];\n const tools = Object.freeze([...(args.tools ?? [])]);\n return Object.freeze({\n metadata: args.metadata,\n source: args.source,\n ...(args.basePath === undefined ? {} : { basePath: args.basePath }),\n trustPolicy: args.trustPolicy,\n ...(args.signature === undefined ? {} : { signature: args.signature }),\n async body(_signal?: AbortSignal): Promise<string> {\n if (bodyCache !== null) return bodyCache;\n if (bodyPromise === null) {\n bodyPromise = args.bodyLoader().then((value) => {\n bodyCache = value;\n return value;\n });\n }\n return bodyPromise;\n },\n async resources(signal?: AbortSignal): Promise<ReadonlyArray<SkillResource>> {\n if (resourcesCache !== null) return resourcesCache;\n if (resourcesPromise === null) {\n resourcesPromise = args.resourceLoader(signal).then((value) => {\n resourcesCache = value;\n return value;\n });\n }\n return resourcesPromise;\n },\n tools(): ReadonlyArray<InlineSkillTool> {\n return tools;\n },\n toolDeclarations(): ReadonlyArray<SkillToolDeclaration> {\n return toolDeclarations;\n },\n diagnostics(): ReadonlyArray<FrontmatterDiagnostic> {\n return args.diagnostics;\n },\n } satisfies Skill);\n}\n\nfunction describeSource(source: SkillSource): string {\n switch (source.kind) {\n case 'folder':\n return `folder:${source.path}`;\n case 'npm-package':\n return source.version === undefined\n ? `npm:${source.packageName}`\n : `npm:${source.packageName}@${source.version}`;\n case 'git-repo':\n return source.ref === undefined ? `git:${source.url}` : `git:${source.url}@${source.ref}`;\n case 'inline':\n return 'inline';\n default: {\n const exhaustive: never = source;\n void exhaustive;\n return 'unknown';\n }\n }\n}\n\nfunction extractTrustLevel(source: SkillSource): SkillsTrustLevel | undefined {\n if (source.kind === 'npm-package' || source.kind === 'git-repo' || source.kind === 'folder')\n return source.trustLevel;\n return undefined;\n}\n\n/**\n * Cap a folder skill's self-declared trust level. A directory on disk —\n * possibly downloaded from the internet — cannot self-promote to a trusted\n * tier without an operator override (RP-9): `trusted` /\n * `trusted-with-scripts` collapse to `'unknown'` (sandbox forced, signature\n * optional, outputs taint-marked), while `untrusted` / `unknown` pass\n * through unchanged.\n */\nfunction capSelfDeclaredTrust(level: SkillsTrustLevel): SkillsTrustLevel {\n return level === 'trusted' || level === 'trusted-with-scripts' ? 'unknown' : level;\n}\n\n/**\n * Translate the loader's four-valued trust level into the three-\n * valued trust level the supply-chain installer expects.\n *\n * The 'unknown' value collapses to 'untrusted' so the supply-chain\n * resolver picks the strict signature + `--ignore-scripts` policy.\n * The loader keeps the original 'unknown' on the skill's metadata\n * record so the runtime sandbox tier resolver continues to apply\n * the correct policy.\n */\nfunction coerceForSupplyChain(\n level: SkillsTrustLevel,\n): 'trusted' | 'trusted-with-scripts' | 'untrusted' {\n if (level === 'unknown') return 'untrusted';\n return level;\n}\n\nfunction guessMediaType(path: string): string | undefined {\n const ext = extname(path).toLowerCase();\n return DEFAULT_MEDIA_TYPES[ext];\n}\n\n/**\n * Required handoff-filter declaration helper. Returns the typed\n * declaration the loader parsed from frontmatter; throws\n * {@link InputFilterRequiredError} when the skill is untrusted and the\n * field is missing. Used by the agent runtime in Phase 12 right\n * before instantiating an untrusted skill's sub-agent.\n *\n * @stable\n */\nexport function requireHandoffInputFilter(metadata: SkillMetadata): HandoffInputFilterDeclaration {\n if (metadata.graphorinHandoffInputFilter !== undefined) {\n if (\n metadata.graphorinTrustLevel === 'untrusted' &&\n metadata.graphorinHandoffInputFilter.kind === 'full'\n ) {\n // ADR-040: `full` is rejected for untrusted skills regardless\n // of declaration. Fall back to the bounded default.\n return Object.freeze({ kind: 'lastUser' });\n }\n return metadata.graphorinHandoffInputFilter;\n }\n if (metadata.graphorinTrustLevel === 'untrusted') {\n throw new InputFilterRequiredError(metadata.name);\n }\n // `'unknown'` skills require an explicit declaration too — default-\n // deny posture per Phase 08. Trusted skills inherit the framework's\n // bounded `lastN(10)` default.\n if (metadata.graphorinTrustLevel === 'unknown') {\n throw new InputFilterRequiredError(metadata.name);\n }\n return Object.freeze({ kind: 'lastN', n: 10 });\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ //#region src/migration/index.d.ts
2
+ /**
3
+ * `migrate-frontmatter` — idempotent rewrite helper that migrates
4
+ * legacy `graphorin-*` frontmatter fields onto their upstream
5
+ * equivalents per the `deprecate-graphorin-prefix` mappings recorded
6
+ * in the bundled spec snapshot.
7
+ *
8
+ * The function is dry-run by default — callers must opt in to
9
+ * persisting the rewritten bytes. The CLI binary in Phase 15 wraps
10
+ * this surface; the library is exposed here so other tooling can
11
+ * reuse it.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ /** A single rewrite the migrator applied (or would apply in a dry-run). */
16
+ interface MigrationRewrite {
17
+ readonly fromField: string;
18
+ readonly toField: string;
19
+ readonly reason: 'deprecate-graphorin-prefix' | 'co-exist-noop' | 'graphorin-only-noop';
20
+ readonly applied: boolean;
21
+ }
22
+ /** Result of a single SKILL.md migration. */
23
+ interface MigrationResult {
24
+ readonly skillId: string;
25
+ readonly rewrites: ReadonlyArray<MigrationRewrite>;
26
+ readonly originalSkillMd: string;
27
+ readonly migratedSkillMd: string;
28
+ readonly changed: boolean;
29
+ }
30
+ /** Options accepted by {@link migrateFrontmatter}. */
31
+ interface MigrateFrontmatterOptions {
32
+ /** Identifier used in audit / error messages. Defaults to `'<inline>'`. */
33
+ readonly skillId?: string;
34
+ /**
35
+ * When `true`, rewrites are applied to the returned `migratedSkillMd`.
36
+ * When `false` (default), `migratedSkillMd === originalSkillMd` and
37
+ * the function operates as a dry-run report.
38
+ */
39
+ readonly apply?: boolean;
40
+ }
41
+ /**
42
+ * Migrate the bundled `deprecate-graphorin-prefix` mappings on a
43
+ * single SKILL.md. The function is idempotent: re-running it on an
44
+ * already-migrated SKILL.md returns `changed: false` and an empty
45
+ * `rewrites` array.
46
+ *
47
+ * @stable
48
+ */
49
+ declare function migrateFrontmatter(skillMd: string, options?: MigrateFrontmatterOptions): MigrationResult;
50
+ /**
51
+ * Stable key ordering: Anthropic-base fields first (in their snapshot
52
+ * insertion order), then the `metadata` bucket, then the
53
+ * `graphorin-*` fields, then anything else. The migrator emits in
54
+ * this order so re-running the migrator on the same input yields
55
+ * identical bytes (idempotence).
56
+ *
57
+ * @stable
58
+ */
59
+ declare function sortKeysAnthropicFirst(frontmatter: Record<string, unknown>): Record<string, unknown>;
60
+ //#endregion
61
+ export { MigrateFrontmatterOptions, MigrationResult, MigrationRewrite, migrateFrontmatter, sortKeysAnthropicFirst };
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/migration/index.ts"],"sourcesContent":[],"mappings":";;AAqBA;AAQA;AASA;AAmBA;AAiHA;;;;;;;;;UArJiB,gBAAA;;;;;;;UAQA,eAAA;;qBAEI,cAAc;;;;;;UAOlB,yBAAA;;;;;;;;;;;;;;;;;;iBAmBD,kBAAA,4BAEL,4BACR;;;;;;;;;;iBA8Ga,sBAAA,cACD,0BACZ"}