agent-skills-standard 2.4.7 → 2.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/README.md CHANGED
@@ -126,6 +126,7 @@ The CLI **never** modifies user-home files (`~/.cursor/mcp.json`, `~/.gemini/set
126
126
 
127
127
  | Version | Date | Skills | Avg Tokens | Savings (%) | Report |
128
128
  | --- | --- | --- | --- | --- | --- |
129
+ | v2.4.7 | 2026-06-15 | 251 | 551 | 85% | [Report](benchmarks/archive/v2.4.7.md) |
129
130
  | v2.4.6 | 2026-06-10 | 251 | 548 | 85% | [Report](benchmarks/archive/v2.4.6.md) |
130
131
  | v2.4.1 | 2026-05-18 | 247 | 540 | 85% | [Report](benchmarks/archive/v2.4.1.md) |
131
132
  | v2.4.0 | 2026-05-14 | 246 | 540 | 85% | [Report](benchmarks/archive/v2.4.0.md) |
@@ -135,4 +136,3 @@ The CLI **never** modifies user-home files (`~/.cursor/mcp.json`, `~/.gemini/set
135
136
  | v2.1.2 | 2026-04-11 | 237 | 516 | 86% | [Report](benchmarks/archive/v2.1.2.md) |
136
137
  | v2.1.1 | 2026-04-11 | 237 | 516 | 86% | [Report](benchmarks/archive/v2.1.1.md) |
137
138
  | v2.1.0 | 2026-04-04 | 237 | 526 | 86% | [Report](benchmarks/archive/v2.1.0.md) |
138
- | v2.0.1 | 2026-03-30 | 238 | 527 | 86% | [Report](benchmarks/archive/v2.0.1.md) |
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ let child_process = require("child_process");
43
43
  let fs = require("fs");
44
44
  var package_default = {
45
45
  name: "agent-skills-standard",
46
- version: "2.4.7",
46
+ version: "2.5.0",
47
47
  description: "A CLI to manage and sync AI agent skills standard for Cursor, Claude, Copilot, Windsurf, and more.",
48
48
  main: "dist/index.js",
49
49
  bin: {
@@ -83,7 +83,7 @@ var package_default = {
83
83
  "dotenv": "^17.2.3",
84
84
  "fs-extra": "^11.2.0",
85
85
  "inquirer": "^8.2.5",
86
- "js-yaml": "^4.1.0",
86
+ "js-yaml": "^4.2.0",
87
87
  "picocolors": "^1.0.0",
88
88
  "zod": "^3.22.4"
89
89
  },
@@ -1042,11 +1042,181 @@ var SkillConfigSchema = zod.z.object({
1042
1042
  mcp: McpConfigSchema.optional()
1043
1043
  });
1044
1044
  var DEFAULT_SDLC_SUPPORT_CATEGORIES = ["quality-engineering"];
1045
+ var PRIMARY_CONFIG_FILE = ".skillsrc";
1046
+ var LEGACY_YAML_CONFIG_FILE = ".skillsrc.yaml";
1045
1047
  /**
1046
1048
  * Service for managing the `.skillsrc` configuration file.
1047
1049
  * Handles loading, saving, and initial construction of the configuration based on project metadata.
1048
1050
  */
1049
1051
  var ConfigService = class {
1052
+ getPrimaryConfigPath(cwd) {
1053
+ return path.default.join(cwd, PRIMARY_CONFIG_FILE);
1054
+ }
1055
+ getLegacyYamlConfigPath(cwd) {
1056
+ return path.default.join(cwd, LEGACY_YAML_CONFIG_FILE);
1057
+ }
1058
+ async resolveConfigPath(cwd) {
1059
+ const primaryPath = this.getPrimaryConfigPath(cwd);
1060
+ if (await fs_extra.default.pathExists(primaryPath)) return primaryPath;
1061
+ const legacyYamlPath = this.getLegacyYamlConfigPath(cwd);
1062
+ if (await fs_extra.default.pathExists(legacyYamlPath)) return legacyYamlPath;
1063
+ return null;
1064
+ }
1065
+ dumpConfig(config) {
1066
+ return js_yaml.default.dump(config, {
1067
+ noRefs: true,
1068
+ lineWidth: -1
1069
+ });
1070
+ }
1071
+ isRecord(value) {
1072
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1073
+ }
1074
+ stripAngleBrackets(value) {
1075
+ const trimmed = value.trim();
1076
+ if (trimmed.startsWith("<") && trimmed.endsWith(">")) return trimmed.slice(1, -1).trim();
1077
+ return trimmed;
1078
+ }
1079
+ parseLegacyList(value) {
1080
+ const trimmed = value.trim();
1081
+ if (!trimmed || trimmed === "[]") return [];
1082
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) return trimmed.slice(1, -1).split(",").map((item) => item.trim()).filter(Boolean);
1083
+ if (trimmed.startsWith("- ")) return trimmed.replace(/^-\s+/, "").split(/\s+-\s+/).map((item) => item.trim()).filter(Boolean);
1084
+ return trimmed.split(",").map((item) => item.trim()).filter(Boolean);
1085
+ }
1086
+ parseLegacyBoolean(value) {
1087
+ const normalized = value.trim().toLowerCase();
1088
+ if (normalized === "true") return true;
1089
+ if (normalized === "false") return false;
1090
+ }
1091
+ migrateLegacyTextConfig(content) {
1092
+ const lines = content.split(/\r?\n/);
1093
+ const registryLine = lines.find((line) => line.trim().startsWith("registry:"));
1094
+ const agentsIndex = lines.findIndex((line) => line.trim() === "agents:");
1095
+ const skillsIndex = lines.findIndex((line) => line.trim() === "skills:");
1096
+ if (!registryLine || agentsIndex === -1 || skillsIndex === -1) return null;
1097
+ const registryMatch = registryLine.match(/^\s*registry:\s*(.+)$/);
1098
+ if (!registryMatch) return null;
1099
+ const config = {
1100
+ registry: this.stripAngleBrackets(registryMatch[1]),
1101
+ agents: [],
1102
+ skills: {}
1103
+ };
1104
+ const agents = [];
1105
+ for (let index = agentsIndex + 1; index < skillsIndex; index += 1) {
1106
+ const match = lines[index].match(/^\s*-\s+(.+)$/);
1107
+ if (!match) continue;
1108
+ agents.push(match[1].trim());
1109
+ }
1110
+ config.agents = agents;
1111
+ const sectionNames = new Set([
1112
+ "custom_overrides",
1113
+ "workflows",
1114
+ "mcp"
1115
+ ]);
1116
+ let currentCategory = null;
1117
+ for (let index = skillsIndex + 1; index < lines.length; index += 1) {
1118
+ const trimmed = lines[index].trim();
1119
+ if (!trimmed) continue;
1120
+ if (trimmed === "workflows:" || trimmed === "mcp:" || trimmed.startsWith("custom_overrides:")) break;
1121
+ const categoryMatch = trimmed.match(/^([A-Za-z0-9-]+):$/);
1122
+ if (categoryMatch && !sectionNames.has(categoryMatch[1])) {
1123
+ currentCategory = categoryMatch[1];
1124
+ config.skills[currentCategory] = {};
1125
+ continue;
1126
+ }
1127
+ if (!currentCategory) continue;
1128
+ const refMatch = trimmed.match(/^ref:\s*(.+)$/);
1129
+ if (refMatch) {
1130
+ config.skills[currentCategory].ref = refMatch[1].trim();
1131
+ continue;
1132
+ }
1133
+ const excludeMatch = trimmed.match(/^exclude:\s*(.+)$/);
1134
+ if (excludeMatch) {
1135
+ config.skills[currentCategory].exclude = this.parseLegacyList(excludeMatch[1]);
1136
+ continue;
1137
+ }
1138
+ const includeMatch = trimmed.match(/^include:\s*(.+)$/);
1139
+ if (includeMatch) config.skills[currentCategory].include = this.parseLegacyList(includeMatch[1]);
1140
+ }
1141
+ const overridesLine = lines.find((line) => line.trim().startsWith("custom_overrides:"));
1142
+ if (overridesLine) {
1143
+ const match = overridesLine.match(/^\s*custom_overrides:\s*(.*)$/);
1144
+ config.custom_overrides = this.parseLegacyList(match?.[1] ?? "");
1145
+ }
1146
+ const workflowsIndex = lines.findIndex((line) => line.trim() === "workflows:");
1147
+ if (workflowsIndex !== -1) {
1148
+ const workflows = [];
1149
+ for (let index = workflowsIndex + 1; index < lines.length; index += 1) {
1150
+ const trimmed = lines[index].trim();
1151
+ if (!trimmed) continue;
1152
+ if (trimmed === "mcp:") break;
1153
+ const match = trimmed.match(/^-\s+(.+)$/);
1154
+ if (match) workflows.push(match[1].trim());
1155
+ }
1156
+ config.workflows = workflows;
1157
+ }
1158
+ const mcpIndex = lines.findIndex((line) => line.trim() === "mcp:");
1159
+ if (mcpIndex !== -1) {
1160
+ const mcp = {};
1161
+ for (let index = mcpIndex + 1; index < lines.length; index += 1) {
1162
+ const trimmed = lines[index].trim();
1163
+ if (!trimmed) continue;
1164
+ const match = trimmed.match(/^([A-Za-z0-9_]+):\s*(.+)$/);
1165
+ if (!match) continue;
1166
+ const [, key, rawValue] = match;
1167
+ if (key === "enabled" || key === "prompted" || key === "snippets") mcp[key] = this.parseLegacyBoolean(rawValue);
1168
+ else mcp[key] = rawValue.trim();
1169
+ }
1170
+ config.mcp = mcp;
1171
+ }
1172
+ return config;
1173
+ }
1174
+ normalizeRawConfig(rawConfig) {
1175
+ if (!this.isRecord(rawConfig)) return {
1176
+ config: {},
1177
+ migrated: false
1178
+ };
1179
+ const normalized = { ...rawConfig };
1180
+ let migrated = false;
1181
+ if (typeof normalized.registry === "string") {
1182
+ const strippedRegistry = this.stripAngleBrackets(normalized.registry);
1183
+ if (strippedRegistry !== normalized.registry) {
1184
+ normalized.registry = strippedRegistry;
1185
+ migrated = true;
1186
+ }
1187
+ }
1188
+ if (Array.isArray(normalized.agents)) normalized.agents = normalized.agents.map((agent) => {
1189
+ if (typeof agent === "string" && agent.toLowerCase() === "openai") {
1190
+ migrated = true;
1191
+ return Agent.Codex;
1192
+ }
1193
+ return agent;
1194
+ });
1195
+ if (this.isRecord(normalized.skills)) {
1196
+ const normalizedSkills = {};
1197
+ for (const [category, value] of Object.entries(normalized.skills)) {
1198
+ if (!this.isRecord(value)) {
1199
+ normalizedSkills[category] = {};
1200
+ migrated = true;
1201
+ continue;
1202
+ }
1203
+ const entry = {};
1204
+ if (typeof value.ref === "string") entry.ref = value.ref;
1205
+ if (Array.isArray(value.include)) entry.include = value.include.filter((item) => typeof item === "string");
1206
+ if (Array.isArray(value.exclude)) entry.exclude = value.exclude.filter((item) => typeof item === "string");
1207
+ else if (typeof value.exclude === "string") {
1208
+ entry.exclude = this.parseLegacyList(value.exclude);
1209
+ migrated = true;
1210
+ }
1211
+ normalizedSkills[category] = entry;
1212
+ }
1213
+ normalized.skills = normalizedSkills;
1214
+ }
1215
+ return {
1216
+ config: normalized,
1217
+ migrated
1218
+ };
1219
+ }
1050
1220
  getCategoryRef(category, metadata) {
1051
1221
  const categoryMetadata = metadata.categories?.[category];
1052
1222
  if (!categoryMetadata?.version) return "main";
@@ -1059,27 +1229,26 @@ var ConfigService = class {
1059
1229
  * @throws Error if the configuration format is invalid
1060
1230
  */
1061
1231
  async loadConfig(cwd = process.cwd()) {
1062
- const configPath = path.default.join(cwd, ".skillsrc");
1063
- if (!await fs_extra.default.pathExists(configPath)) return null;
1232
+ const configPath = await this.resolveConfigPath(cwd);
1233
+ if (!configPath) return null;
1234
+ const primaryConfigPath = this.getPrimaryConfigPath(cwd);
1235
+ const shouldRenameLegacyYamlFile = configPath !== primaryConfigPath;
1064
1236
  try {
1065
1237
  const content = await fs_extra.default.readFile(configPath, "utf8");
1066
- const rawConfig = js_yaml.default.load(content);
1067
- if (rawConfig && Array.isArray(rawConfig.agents)) {
1068
- let needsMigration = false;
1069
- const updatedAgents = rawConfig.agents.map((agent) => {
1070
- if (typeof agent === "string" && agent.toLowerCase() === "openai") {
1071
- needsMigration = true;
1072
- return Agent.Codex;
1073
- }
1074
- return agent;
1075
- });
1076
- if (needsMigration) {
1077
- rawConfig.agents = updatedAgents;
1078
- await fs_extra.default.outputFile(configPath, js_yaml.default.dump(rawConfig));
1079
- }
1238
+ let rawConfig;
1239
+ let migratedFromText = false;
1240
+ try {
1241
+ rawConfig = js_yaml.default.load(content);
1242
+ } catch (parseError) {
1243
+ rawConfig = this.migrateLegacyTextConfig(content);
1244
+ migratedFromText = rawConfig !== null;
1245
+ if (!migratedFromText) throw parseError;
1080
1246
  }
1081
- const parsed = SkillConfigSchema.safeParse(rawConfig);
1247
+ const { config: normalizedConfig, migrated } = this.normalizeRawConfig(rawConfig);
1248
+ const parsed = SkillConfigSchema.safeParse(normalizedConfig);
1082
1249
  if (!parsed.success) throw new Error(`Invalid .skillsrc format: ${parsed.error.message}`);
1250
+ if (migratedFromText || migrated || shouldRenameLegacyYamlFile) await fs_extra.default.outputFile(primaryConfigPath, this.dumpConfig(parsed.data));
1251
+ if (shouldRenameLegacyYamlFile) await fs_extra.default.remove(configPath);
1083
1252
  return parsed.data;
1084
1253
  } catch (error) {
1085
1254
  throw new Error(`Failed to load config: ${error}`);
@@ -1091,8 +1260,10 @@ var ConfigService = class {
1091
1260
  * @param cwd Current working directory
1092
1261
  */
1093
1262
  async saveConfig(config, cwd = process.cwd()) {
1094
- const configPath = path.default.join(cwd, ".skillsrc");
1095
- await fs_extra.default.outputFile(configPath, js_yaml.default.dump(config));
1263
+ const primaryConfigPath = this.getPrimaryConfigPath(cwd);
1264
+ const legacyYamlPath = this.getLegacyYamlConfigPath(cwd);
1265
+ await fs_extra.default.outputFile(primaryConfigPath, this.dumpConfig(config));
1266
+ if (legacyYamlPath !== primaryConfigPath && await fs_extra.default.pathExists(legacyYamlPath)) await fs_extra.default.remove(legacyYamlPath);
1096
1267
  }
1097
1268
  /**
1098
1269
  * Constructs an initial configuration object based on project analysis.
@@ -1289,7 +1460,9 @@ process.stdin.on('end', () => {
1289
1460
  '[SKILL TRIGGER] Editing: ' + fileName + '\\n' +
1290
1461
  '-> Call load_skills_for_files(files=[ "' + filePath + '" ]) on the ' +
1291
1462
  'agent-skills-standard MCP. It returns applicable SKILL.md rules, ' +
1292
- 'or nothing if no skills match this file type.'
1463
+ 'or nothing if no skills match this file type.\\n' +
1464
+ '-> If this work spans a whole framework or migration, also call ' +
1465
+ 'get_category_guide(category="...") for the framework-level map.'
1293
1466
  );
1294
1467
  process.exit(0);
1295
1468
  } catch {