agentpacks 0.9.0 → 1.0.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 (67) hide show
  1. package/README.md +41 -14
  2. package/dist/api.js +403 -179
  3. package/dist/cli/export-cmd.js +58 -5
  4. package/dist/cli/generate.js +268 -59
  5. package/dist/cli/import-cmd.js +203 -95
  6. package/dist/cli/install.js +1 -0
  7. package/dist/cli/models-explain.js +56 -0
  8. package/dist/cli/pack/list.js +56 -0
  9. package/dist/cli/pack/validate.js +143 -18
  10. package/dist/cli/publish.js +1 -0
  11. package/dist/core/config.d.ts +1 -1
  12. package/dist/core/config.js +1 -0
  13. package/dist/core/index.js +56 -0
  14. package/dist/core/metarepo.js +1 -0
  15. package/dist/core/pack-loader.js +56 -0
  16. package/dist/exporters/cursor-plugin.js +109 -22
  17. package/dist/exporters/index.js +109 -22
  18. package/dist/features/index.d.ts +1 -1
  19. package/dist/features/index.js +59 -0
  20. package/dist/features/skills.d.ts +22 -0
  21. package/dist/features/skills.js +60 -1
  22. package/dist/importers/cursor.js +122 -26
  23. package/dist/importers/opencode.js +138 -24
  24. package/dist/importers/rulesync.js +147 -33
  25. package/dist/index.js +484 -244
  26. package/dist/node/api.js +403 -179
  27. package/dist/node/cli/export-cmd.js +58 -5
  28. package/dist/node/cli/generate.js +268 -59
  29. package/dist/node/cli/import-cmd.js +203 -95
  30. package/dist/node/cli/install.js +1 -0
  31. package/dist/node/cli/models-explain.js +56 -0
  32. package/dist/node/cli/pack/list.js +56 -0
  33. package/dist/node/cli/pack/validate.js +143 -18
  34. package/dist/node/cli/publish.js +1 -0
  35. package/dist/node/core/config.js +1 -0
  36. package/dist/node/core/index.js +56 -0
  37. package/dist/node/core/metarepo.js +1 -0
  38. package/dist/node/core/pack-loader.js +56 -0
  39. package/dist/node/exporters/cursor-plugin.js +109 -22
  40. package/dist/node/exporters/index.js +109 -22
  41. package/dist/node/features/index.js +59 -0
  42. package/dist/node/features/skills.js +60 -1
  43. package/dist/node/importers/cursor.js +122 -26
  44. package/dist/node/importers/opencode.js +138 -24
  45. package/dist/node/importers/rulesync.js +147 -33
  46. package/dist/node/index.js +484 -244
  47. package/dist/node/targets/claude-code.js +56 -1
  48. package/dist/node/targets/codex-cli.js +56 -1
  49. package/dist/node/targets/copilot.js +56 -1
  50. package/dist/node/targets/cursor.js +56 -5
  51. package/dist/node/targets/index.js +268 -59
  52. package/dist/node/targets/mistral-vibe.js +661 -0
  53. package/dist/node/targets/opencode.js +56 -1
  54. package/dist/node/targets/registry.js +267 -59
  55. package/dist/node/utils/model-allowlist.js +6 -2
  56. package/dist/targets/claude-code.js +56 -1
  57. package/dist/targets/codex-cli.js +56 -1
  58. package/dist/targets/copilot.js +56 -1
  59. package/dist/targets/cursor.js +56 -5
  60. package/dist/targets/index.d.ts +1 -0
  61. package/dist/targets/index.js +268 -59
  62. package/dist/targets/mistral-vibe.d.ts +13 -0
  63. package/dist/targets/mistral-vibe.js +661 -0
  64. package/dist/targets/opencode.js +56 -1
  65. package/dist/targets/registry.js +267 -59
  66. package/dist/utils/model-allowlist.js +6 -2
  67. package/package.json +15 -3
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
351
352
  // src/features/skills.ts
352
353
  import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
353
354
  import { basename as basename4, join as join2 } from "path";
355
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
356
+ var SKILL_NAME_MAX_LENGTH = 64;
354
357
  function parseSkills(skillsDir, packName) {
355
358
  const dirs = listDirs(skillsDir);
356
359
  const skills = [];
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
374
377
  content
375
378
  };
376
379
  }
380
+ function buildSkillFrontmatter(skill) {
381
+ return {
382
+ ...skill.meta,
383
+ name: skill.name
384
+ };
385
+ }
386
+ function serializeSkill(skill) {
387
+ return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
388
+ }
389
+ function normalizeImportedSkillMarkdown(source, skillName) {
390
+ const { data, content } = parseFrontmatter(source);
391
+ const normalized = {
392
+ ...data,
393
+ name: skillName
394
+ };
395
+ let addedDescription = false;
396
+ const description = normalized.description;
397
+ if (typeof description !== "string" || description.trim().length === 0) {
398
+ normalized.description = `Imported skill: ${skillName}`;
399
+ addedDescription = true;
400
+ }
401
+ return {
402
+ content: serializeFrontmatter(normalized, content),
403
+ addedDescription
404
+ };
405
+ }
406
+ function validateAgentSkillsFrontmatter(skill) {
407
+ const errors = [];
408
+ const dirName = basename4(skill.sourceDir);
409
+ const declaredName = skill.meta.name;
410
+ if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
411
+ errors.push('Missing required frontmatter field "name".');
412
+ } else {
413
+ if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
414
+ errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
415
+ }
416
+ if (!SKILL_NAME_PATTERN.test(declaredName)) {
417
+ errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
418
+ }
419
+ if (declaredName !== dirName) {
420
+ errors.push(`Invalid "name": must match containing directory "${dirName}".`);
421
+ }
422
+ }
423
+ const description = skill.meta.description;
424
+ if (typeof description !== "string" || description.trim().length === 0) {
425
+ errors.push('Missing required frontmatter field "description".');
426
+ }
427
+ const allowedTools = skill.meta["allowed-tools"];
428
+ if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
429
+ errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
430
+ }
431
+ return errors;
432
+ }
377
433
  function skillMatchesTarget(skill, targetId) {
378
434
  const { targets } = skill.meta;
379
435
  if (!targets || targets === "*")
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -243,8 +244,116 @@ function getHeader(type) {
243
244
  }
244
245
  }
245
246
 
247
+ // src/utils/frontmatter.ts
248
+ import matter from "gray-matter";
249
+ function parseFrontmatter(source) {
250
+ const { data, content } = matter(source);
251
+ return {
252
+ data,
253
+ content: content.trim(),
254
+ raw: source
255
+ };
256
+ }
257
+ function serializeFrontmatter(data, content) {
258
+ const filtered = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));
259
+ if (Object.keys(filtered).length === 0) {
260
+ return content;
261
+ }
262
+ return matter.stringify(content, filtered);
263
+ }
264
+
265
+ // src/features/skills.ts
266
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
267
+ import { basename, join as join2 } from "path";
268
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
269
+ var SKILL_NAME_MAX_LENGTH = 64;
270
+ function parseSkills(skillsDir, packName) {
271
+ const dirs = listDirs(skillsDir);
272
+ const skills = [];
273
+ for (const dir of dirs) {
274
+ const skillMd = join2(dir, "SKILL.md");
275
+ if (existsSync3(skillMd)) {
276
+ skills.push(parseSkillFile(skillMd, dir, packName));
277
+ }
278
+ }
279
+ return skills;
280
+ }
281
+ function parseSkillFile(filepath, skillDir, packName) {
282
+ const raw = readFileSync3(filepath, "utf-8");
283
+ const { data, content } = parseFrontmatter(raw);
284
+ return {
285
+ name: data.name ?? basename(skillDir),
286
+ sourcePath: filepath,
287
+ sourceDir: skillDir,
288
+ packName,
289
+ meta: data,
290
+ content
291
+ };
292
+ }
293
+ function buildSkillFrontmatter(skill) {
294
+ return {
295
+ ...skill.meta,
296
+ name: skill.name
297
+ };
298
+ }
299
+ function serializeSkill(skill) {
300
+ return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
301
+ }
302
+ function normalizeImportedSkillMarkdown(source, skillName) {
303
+ const { data, content } = parseFrontmatter(source);
304
+ const normalized = {
305
+ ...data,
306
+ name: skillName
307
+ };
308
+ let addedDescription = false;
309
+ const description = normalized.description;
310
+ if (typeof description !== "string" || description.trim().length === 0) {
311
+ normalized.description = `Imported skill: ${skillName}`;
312
+ addedDescription = true;
313
+ }
314
+ return {
315
+ content: serializeFrontmatter(normalized, content),
316
+ addedDescription
317
+ };
318
+ }
319
+ function validateAgentSkillsFrontmatter(skill) {
320
+ const errors = [];
321
+ const dirName = basename(skill.sourceDir);
322
+ const declaredName = skill.meta.name;
323
+ if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
324
+ errors.push('Missing required frontmatter field "name".');
325
+ } else {
326
+ if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
327
+ errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
328
+ }
329
+ if (!SKILL_NAME_PATTERN.test(declaredName)) {
330
+ errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
331
+ }
332
+ if (declaredName !== dirName) {
333
+ errors.push(`Invalid "name": must match containing directory "${dirName}".`);
334
+ }
335
+ }
336
+ const description = skill.meta.description;
337
+ if (typeof description !== "string" || description.trim().length === 0) {
338
+ errors.push('Missing required frontmatter field "description".');
339
+ }
340
+ const allowedTools = skill.meta["allowed-tools"];
341
+ if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
342
+ errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
343
+ }
344
+ return errors;
345
+ }
346
+ function skillMatchesTarget(skill, targetId) {
347
+ const { targets } = skill.meta;
348
+ if (!targets || targets === "*")
349
+ return true;
350
+ if (Array.isArray(targets) && targets.includes("*"))
351
+ return true;
352
+ return Array.isArray(targets) && targets.includes(targetId);
353
+ }
354
+
246
355
  // src/features/models.ts
247
- import { join as join2 } from "path";
356
+ import { join as join3 } from "path";
248
357
  import { z as z2 } from "zod";
249
358
  var SECRET_PATTERNS = [
250
359
  /["']api[_-]?key["']\s*:/i,
@@ -304,7 +413,7 @@ var ModelsSchema = z2.object({
304
413
  })).optional()
305
414
  });
306
415
  function parseModels(packDir, packName) {
307
- const modelsPath = join2(packDir, "models.json");
416
+ const modelsPath = join3(packDir, "models.json");
308
417
  const raw = readJsonOrNull(modelsPath);
309
418
  if (!raw)
310
419
  return null;
@@ -413,8 +522,8 @@ function scanModelsForSecrets(config) {
413
522
  }
414
523
 
415
524
  // src/cli/pack/validate.ts
416
- import { existsSync as existsSync3 } from "fs";
417
- import { resolve as resolve2, join as join3 } from "path";
525
+ import { existsSync as existsSync4 } from "fs";
526
+ import { resolve as resolve2, join as join4, basename as basename2 } from "path";
418
527
  import chalk from "chalk";
419
528
  function runPackValidate(projectRoot) {
420
529
  const config = loadWorkspaceConfig(projectRoot);
@@ -423,13 +532,13 @@ function runPackValidate(projectRoot) {
423
532
  const packDir = resolvePackDir(projectRoot, packRef);
424
533
  console.log(chalk.bold(`
425
534
  Validating pack: ${packRef}`));
426
- if (!packDir || !existsSync3(packDir)) {
535
+ if (!packDir || !existsSync4(packDir)) {
427
536
  console.log(chalk.red(` ERROR: Pack directory not found: ${packRef}`));
428
537
  hasErrors = true;
429
538
  continue;
430
539
  }
431
540
  const packJsonPath = resolve2(packDir, "pack.json");
432
- if (!existsSync3(packJsonPath)) {
541
+ if (!existsSync4(packJsonPath)) {
433
542
  console.log(chalk.yellow(" warn: No pack.json found. Name will be inferred from directory."));
434
543
  } else {
435
544
  try {
@@ -443,14 +552,30 @@ Validating pack: ${packRef}`));
443
552
  }
444
553
  const subdirs = ["rules", "commands", "agents", "skills"];
445
554
  for (const sub of subdirs) {
446
- const subDir = join3(packDir, sub);
447
- if (existsSync3(subDir)) {
555
+ const subDir = join4(packDir, sub);
556
+ if (existsSync4(subDir)) {
448
557
  if (sub === "skills") {
449
558
  const skillDirs = listDirs(subDir);
450
559
  for (const skillDir of skillDirs) {
451
- const skillMd = join3(skillDir, "SKILL.md");
452
- if (!existsSync3(skillMd)) {
453
- console.log(chalk.yellow(` warn: skills/${skillDir.split("/").pop()} missing SKILL.md`));
560
+ const skillName = basename2(skillDir);
561
+ const skillMd = join4(skillDir, "SKILL.md");
562
+ if (!existsSync4(skillMd)) {
563
+ console.log(chalk.yellow(` warn: skills/${skillName} missing SKILL.md`));
564
+ continue;
565
+ }
566
+ try {
567
+ const parsed = parseSkillFile(skillMd, skillDir, "__validation__");
568
+ const skillErrors = validateAgentSkillsFrontmatter(parsed);
569
+ for (const err of skillErrors) {
570
+ console.log(chalk.red(` ERROR skills/${skillName}/SKILL.md: ${err}`));
571
+ }
572
+ if (skillErrors.length > 0) {
573
+ hasErrors = true;
574
+ }
575
+ } catch (err) {
576
+ const message = err instanceof Error ? err.message : String(err);
577
+ console.log(chalk.red(` ERROR skills/${skillName}/SKILL.md: failed to parse (${message})`));
578
+ hasErrors = true;
454
579
  }
455
580
  }
456
581
  console.log(chalk.green(` ${sub}/: ${skillDirs.length} skill(s)`));
@@ -462,24 +587,24 @@ Validating pack: ${packRef}`));
462
587
  }
463
588
  const optionalFiles = ["mcp.json", "ignore", ".aiignore"];
464
589
  for (const file of optionalFiles) {
465
- if (existsSync3(join3(packDir, file))) {
590
+ if (existsSync4(join4(packDir, file))) {
466
591
  console.log(chalk.green(` ${file}: present`));
467
592
  }
468
593
  }
469
- const hooksJson = join3(packDir, "hooks", "hooks.json");
470
- if (existsSync3(hooksJson)) {
594
+ const hooksJson = join4(packDir, "hooks", "hooks.json");
595
+ if (existsSync4(hooksJson)) {
471
596
  console.log(chalk.green(" hooks/hooks.json: present"));
472
597
  }
473
- const pluginsDir = join3(packDir, "plugins");
474
- if (existsSync3(pluginsDir)) {
598
+ const pluginsDir = join4(packDir, "plugins");
599
+ if (existsSync4(pluginsDir)) {
475
600
  const pluginFiles = [
476
601
  ...listFiles(pluginsDir, { extension: ".ts" }),
477
602
  ...listFiles(pluginsDir, { extension: ".js" })
478
603
  ];
479
604
  console.log(chalk.green(` plugins/: ${pluginFiles.length} file(s)`));
480
605
  }
481
- const modelsJsonPath = join3(packDir, "models.json");
482
- if (existsSync3(modelsJsonPath)) {
606
+ const modelsJsonPath = join4(packDir, "models.json");
607
+ if (existsSync4(modelsJsonPath)) {
483
608
  try {
484
609
  const parsed = parseModels(packDir, "validate");
485
610
  if (parsed) {
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  /**
3
3
  * All supported target identifiers.
4
4
  */
5
- export declare const TARGET_IDS: readonly ["opencode", "cursor", "claudecode", "codexcli", "geminicli", "copilot", "agentsmd", "cline", "kilo", "roo", "qwencode", "kiro", "factorydroid", "antigravity", "junie", "augmentcode", "windsurf", "warp", "replit", "zed"];
5
+ export declare const TARGET_IDS: readonly ["opencode", "cursor", "claudecode", "codexcli", "mistralvibe", "geminicli", "copilot", "agentsmd", "cline", "kilo", "roo", "qwencode", "kiro", "factorydroid", "antigravity", "junie", "augmentcode", "windsurf", "warp", "replit", "zed"];
6
6
  export type TargetId = (typeof TARGET_IDS)[number];
7
7
  /**
8
8
  * All supported feature types.
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
351
352
  // src/features/skills.ts
352
353
  import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
353
354
  import { basename as basename4, join as join2 } from "path";
355
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
356
+ var SKILL_NAME_MAX_LENGTH = 64;
354
357
  function parseSkills(skillsDir, packName) {
355
358
  const dirs = listDirs(skillsDir);
356
359
  const skills = [];
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
374
377
  content
375
378
  };
376
379
  }
380
+ function buildSkillFrontmatter(skill) {
381
+ return {
382
+ ...skill.meta,
383
+ name: skill.name
384
+ };
385
+ }
386
+ function serializeSkill(skill) {
387
+ return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
388
+ }
389
+ function normalizeImportedSkillMarkdown(source, skillName) {
390
+ const { data, content } = parseFrontmatter(source);
391
+ const normalized = {
392
+ ...data,
393
+ name: skillName
394
+ };
395
+ let addedDescription = false;
396
+ const description = normalized.description;
397
+ if (typeof description !== "string" || description.trim().length === 0) {
398
+ normalized.description = `Imported skill: ${skillName}`;
399
+ addedDescription = true;
400
+ }
401
+ return {
402
+ content: serializeFrontmatter(normalized, content),
403
+ addedDescription
404
+ };
405
+ }
406
+ function validateAgentSkillsFrontmatter(skill) {
407
+ const errors = [];
408
+ const dirName = basename4(skill.sourceDir);
409
+ const declaredName = skill.meta.name;
410
+ if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
411
+ errors.push('Missing required frontmatter field "name".');
412
+ } else {
413
+ if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
414
+ errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
415
+ }
416
+ if (!SKILL_NAME_PATTERN.test(declaredName)) {
417
+ errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
418
+ }
419
+ if (declaredName !== dirName) {
420
+ errors.push(`Invalid "name": must match containing directory "${dirName}".`);
421
+ }
422
+ }
423
+ const description = skill.meta.description;
424
+ if (typeof description !== "string" || description.trim().length === 0) {
425
+ errors.push('Missing required frontmatter field "description".');
426
+ }
427
+ const allowedTools = skill.meta["allowed-tools"];
428
+ if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
429
+ errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
430
+ }
431
+ return errors;
432
+ }
377
433
  function skillMatchesTarget(skill, targetId) {
378
434
  const { targets } = skill.meta;
379
435
  if (!targets || targets === "*")
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -11,6 +11,7 @@ var TARGET_IDS = [
11
11
  "cursor",
12
12
  "claudecode",
13
13
  "codexcli",
14
+ "mistralvibe",
14
15
  "geminicli",
15
16
  "copilot",
16
17
  "agentsmd",
@@ -351,6 +352,8 @@ function agentMatchesTarget(agent, targetId) {
351
352
  // src/features/skills.ts
352
353
  import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
353
354
  import { basename as basename4, join as join2 } from "path";
355
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
356
+ var SKILL_NAME_MAX_LENGTH = 64;
354
357
  function parseSkills(skillsDir, packName) {
355
358
  const dirs = listDirs(skillsDir);
356
359
  const skills = [];
@@ -374,6 +377,59 @@ function parseSkillFile(filepath, skillDir, packName) {
374
377
  content
375
378
  };
376
379
  }
380
+ function buildSkillFrontmatter(skill) {
381
+ return {
382
+ ...skill.meta,
383
+ name: skill.name
384
+ };
385
+ }
386
+ function serializeSkill(skill) {
387
+ return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
388
+ }
389
+ function normalizeImportedSkillMarkdown(source, skillName) {
390
+ const { data, content } = parseFrontmatter(source);
391
+ const normalized = {
392
+ ...data,
393
+ name: skillName
394
+ };
395
+ let addedDescription = false;
396
+ const description = normalized.description;
397
+ if (typeof description !== "string" || description.trim().length === 0) {
398
+ normalized.description = `Imported skill: ${skillName}`;
399
+ addedDescription = true;
400
+ }
401
+ return {
402
+ content: serializeFrontmatter(normalized, content),
403
+ addedDescription
404
+ };
405
+ }
406
+ function validateAgentSkillsFrontmatter(skill) {
407
+ const errors = [];
408
+ const dirName = basename4(skill.sourceDir);
409
+ const declaredName = skill.meta.name;
410
+ if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
411
+ errors.push('Missing required frontmatter field "name".');
412
+ } else {
413
+ if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
414
+ errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
415
+ }
416
+ if (!SKILL_NAME_PATTERN.test(declaredName)) {
417
+ errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
418
+ }
419
+ if (declaredName !== dirName) {
420
+ errors.push(`Invalid "name": must match containing directory "${dirName}".`);
421
+ }
422
+ }
423
+ const description = skill.meta.description;
424
+ if (typeof description !== "string" || description.trim().length === 0) {
425
+ errors.push('Missing required frontmatter field "description".');
426
+ }
427
+ const allowedTools = skill.meta["allowed-tools"];
428
+ if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
429
+ errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
430
+ }
431
+ return errors;
432
+ }
377
433
  function skillMatchesTarget(skill, targetId) {
378
434
  const { targets } = skill.meta;
379
435
  if (!targets || targets === "*")