agent-skills-standard 2.5.1 → 2.6.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.6.0 | 2026-07-10 | 264 | 528 | 46% | [Report](benchmarks/archive/v2.6.0.md) |
129
130
  | v2.4.7 | 2026-06-15 | 251 | 551 | 85% | [Report](benchmarks/archive/v2.4.7.md) |
130
131
  | v2.4.6 | 2026-06-10 | 251 | 548 | 85% | [Report](benchmarks/archive/v2.4.6.md) |
131
132
  | v2.4.1 | 2026-05-18 | 247 | 540 | 85% | [Report](benchmarks/archive/v2.4.1.md) |
@@ -135,4 +136,3 @@ The CLI **never** modifies user-home files (`~/.cursor/mcp.json`, `~/.gemini/set
135
136
  | v2.2.0 | 2026-04-22 | 242 | 538 | 85% | [Report](benchmarks/archive/v2.2.0.md) |
136
137
  | v2.1.2 | 2026-04-11 | 237 | 516 | 86% | [Report](benchmarks/archive/v2.1.2.md) |
137
138
  | v2.1.1 | 2026-04-11 | 237 | 516 | 86% | [Report](benchmarks/archive/v2.1.1.md) |
138
- | v2.1.0 | 2026-04-04 | 237 | 526 | 86% | [Report](benchmarks/archive/v2.1.0.md) |
package/dist/index.js CHANGED
@@ -26,14 +26,14 @@ let dotenv = require("dotenv");
26
26
  dotenv = __toESM(dotenv);
27
27
  let picocolors = require("picocolors");
28
28
  picocolors = __toESM(picocolors);
29
- let inquirer = require("inquirer");
30
- inquirer = __toESM(inquirer);
31
29
  let fs_extra = require("fs-extra");
32
30
  fs_extra = __toESM(fs_extra);
33
- let js_yaml = require("js-yaml");
34
- js_yaml = __toESM(js_yaml);
35
31
  let path = require("path");
36
32
  path = __toESM(path);
33
+ let inquirer = require("inquirer");
34
+ inquirer = __toESM(inquirer);
35
+ let js_yaml = require("js-yaml");
36
+ js_yaml = __toESM(js_yaml);
37
37
  let zod = require("zod");
38
38
  let cross_fetch = require("cross-fetch");
39
39
  cross_fetch = __toESM(cross_fetch);
@@ -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.5.1",
46
+ version: "2.6.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: {
@@ -94,6 +94,229 @@ var package_default = {
94
94
  }
95
95
  };
96
96
  //#endregion
97
+ //#region src/services/EvalsVerifier.ts
98
+ function checkAssertion(assertion, transcript) {
99
+ const haystack = transcript.toLowerCase();
100
+ if (assertion.type === "contains_any") return (Array.isArray(assertion.value) ? assertion.value : [assertion.value]).some((value) => haystack.includes(value.toLowerCase()));
101
+ if (assertion.type === "not_contains") return !haystack.includes(String(assertion.value).toLowerCase());
102
+ if (assertion.type === "regex") try {
103
+ return new RegExp(String(assertion.value), "i").test(transcript);
104
+ } catch {
105
+ return false;
106
+ }
107
+ if (assertion.type === "file_reference") {
108
+ const value = String(assertion.value).toLowerCase();
109
+ return haystack.includes(value) || haystack.includes(path.basename(value));
110
+ }
111
+ return haystack.includes(String(assertion.value).toLowerCase());
112
+ }
113
+ function answerPath(runDir, manifest, skill, caseId, arm) {
114
+ const categoryParts = manifest.category === "all" ? [skill.category] : [];
115
+ const filename = arm ? `${caseId}.${arm}.md` : `${caseId}.md`;
116
+ return path.join(runDir, "answers", ...categoryParts, skill.skillName, filename);
117
+ }
118
+ function readAnswer(runDir, manifest, skill, currentCase, arm) {
119
+ const filePath = answerPath(runDir, manifest, skill, currentCase.id, arm);
120
+ return fs_extra.default.existsSync(filePath) ? fs_extra.default.readFileSync(filePath, "utf8") : null;
121
+ }
122
+ function readInputs(runDir) {
123
+ const inputsPath = path.join(runDir, "inputs.json");
124
+ return fs_extra.default.existsSync(inputsPath) ? fs_extra.default.readJSONSync(inputsPath) : null;
125
+ }
126
+ function evalDataFor(projectRoot, runDir, skill) {
127
+ const inputs = readInputs(runDir);
128
+ if (inputs) {
129
+ const source = inputs.sources[`${skill.category}/${skill.skillName}`];
130
+ if (!source) throw new Error(`inputs.json is missing ${skill.category}/${skill.skillName}`);
131
+ return source.evals;
132
+ }
133
+ const evalsPath = path.join(projectRoot, "skills", skill.category, skill.skillName, "evals", "evals.json");
134
+ return fs_extra.default.existsSync(evalsPath) ? fs_extra.default.readJSONSync(evalsPath) : {};
135
+ }
136
+ function passRate(results) {
137
+ return results.length > 0 ? results.filter(Boolean).length / results.length : 0;
138
+ }
139
+ function assertionRate(results) {
140
+ const total = results.reduce((sum, result) => sum + result.total, 0);
141
+ return total > 0 ? results.reduce((sum, result) => sum + result.passed, 0) / total : 0;
142
+ }
143
+ function triggerDecision(transcript) {
144
+ return transcript.match(/^\s*TRIGGER:\s*(yes|no)\s*$/im)?.[1].toLowerCase();
145
+ }
146
+ function summarizeSkill(projectRoot, runDir, manifest, skill) {
147
+ const evalsData = evalDataFor(projectRoot, runDir, skill);
148
+ const evalById = new Map((evalsData.evals ?? []).map((evaluation) => [String(evaluation.id), evaluation]));
149
+ const pressureByIndex = evalsData.pressure_scenarios ?? [];
150
+ const baseline = [];
151
+ const withSkill = [];
152
+ const baselineAssertions = [];
153
+ const withSkillAssertions = [];
154
+ const positive = [];
155
+ const negative = [];
156
+ for (const currentCase of skill.cases) {
157
+ if (currentCase.kind === "trigger") {
158
+ const transcript = readAnswer(runDir, manifest, skill, currentCase);
159
+ if (transcript === null) throw new Error(`missing answer: ${skill.skillName}/${currentCase.id}`);
160
+ const actual = triggerDecision(transcript);
161
+ if ((currentCase.expectedTrigger ?? "no") === "yes") positive.push(actual === "yes");
162
+ else negative.push(actual === "no");
163
+ continue;
164
+ }
165
+ const assertions = currentCase.kind === "eval" ? evalById.get(currentCase.id.replace("eval-", ""))?.assertions ?? [] : (pressureByIndex[Number(currentCase.id.replace("pressure-", "")) - 1]?.behavior_assertions ?? []).map((value) => ({
166
+ type: "contains",
167
+ value
168
+ }));
169
+ for (const arm of ["baseline", "with-skill"]) {
170
+ if (!(arm in currentCase.arms)) continue;
171
+ const transcript = readAnswer(runDir, manifest, skill, currentCase, arm);
172
+ if (transcript === null) throw new Error(`missing answer: ${skill.skillName}/${currentCase.id}.${arm}`);
173
+ const checks = assertions.map((assertion) => checkAssertion(assertion, transcript));
174
+ const passed = checks.every(Boolean);
175
+ (arm === "baseline" ? baseline : withSkill).push(passed);
176
+ (arm === "baseline" ? baselineAssertions : withSkillAssertions).push({
177
+ passed: checks.filter(Boolean).length,
178
+ total: checks.length
179
+ });
180
+ }
181
+ }
182
+ const baselineCompromised = manifest.compromisedSkills?.some((record) => record.category === skill.category && record.skillName === skill.skillName && record.arm === "baseline") ?? false;
183
+ const baselinePassRate = passRate(baseline);
184
+ const withSkillPassRate = passRate(withSkill);
185
+ const triggerRecall = positive.length > 0 ? positive.filter(Boolean).length / positive.length : null;
186
+ const triggerSpecificity = negative.length > 0 ? negative.filter(Boolean).length / negative.length : null;
187
+ const balancedTriggerAccuracy = triggerRecall === null || triggerSpecificity === null ? null : (triggerRecall + triggerSpecificity) / 2;
188
+ return {
189
+ baselinePassRate: baselineCompromised ? "n/a" : baselinePassRate,
190
+ withSkillPassRate,
191
+ delta: baselineCompromised ? "n/a" : withSkillPassRate - baselinePassRate,
192
+ casePassRate: {
193
+ baseline: baselineCompromised ? "n/a" : baselinePassRate,
194
+ withSkill: withSkillPassRate
195
+ },
196
+ assertionPassRate: {
197
+ baseline: baselineCompromised ? "n/a" : assertionRate(baselineAssertions),
198
+ withSkill: assertionRate(withSkillAssertions)
199
+ },
200
+ triggerRecall,
201
+ triggerSpecificity,
202
+ balancedTriggerAccuracy
203
+ };
204
+ }
205
+ function compareMetric(diffs, name, committed, recomputed) {
206
+ if (JSON.stringify(committed) !== JSON.stringify(recomputed)) diffs.push(`${name} committed=${JSON.stringify(committed)} recomputed=${JSON.stringify(recomputed)}`);
207
+ }
208
+ function verifyEvalRun(projectRoot, runId) {
209
+ const runDir = path.join(projectRoot, "benchmarks", "evals", "runs", runId);
210
+ const resultsPath = path.join(runDir, "results.json");
211
+ const manifestPath = path.join(runDir, "manifest.json");
212
+ if (!fs_extra.default.existsSync(runDir)) return {
213
+ runId,
214
+ ok: false,
215
+ reason: `run directory not found: ${runDir}`
216
+ };
217
+ if (!fs_extra.default.existsSync(resultsPath) || !fs_extra.default.existsSync(manifestPath)) return {
218
+ runId,
219
+ ok: false,
220
+ reason: "run is missing manifest.json or results.json"
221
+ };
222
+ const manifest = fs_extra.default.readJSONSync(manifestPath);
223
+ if (manifest.schemaVersion === 2 && readInputs(runDir) === null) return {
224
+ runId,
225
+ ok: false,
226
+ reason: "v2 run is missing immutable inputs.json"
227
+ };
228
+ const committed = fs_extra.default.readJSONSync(resultsPath);
229
+ const diffs = [];
230
+ for (const skill of manifest.skills) {
231
+ let recomputed;
232
+ try {
233
+ recomputed = summarizeSkill(projectRoot, runDir, manifest, skill);
234
+ } catch (error) {
235
+ diffs.push(`${skill.skillName}: ${error instanceof Error ? error.message : String(error)}`);
236
+ continue;
237
+ }
238
+ const committedSkill = committed.skills.find((entry) => entry.skillName === skill.skillName && (!entry.category || entry.category === skill.category));
239
+ if (!committedSkill) {
240
+ diffs.push(`${skill.skillName}: missing from committed results.json`);
241
+ continue;
242
+ }
243
+ if (manifest.schemaVersion === 2 || committed.schemaVersion === 2) {
244
+ compareMetric(diffs, `${skill.skillName}.baselinePassRate`, committedSkill.baselinePassRate, recomputed.baselinePassRate);
245
+ compareMetric(diffs, `${skill.skillName}.withSkillPassRate`, committedSkill.withSkillPassRate, recomputed.withSkillPassRate);
246
+ compareMetric(diffs, `${skill.skillName}.delta`, committedSkill.delta, recomputed.delta);
247
+ compareMetric(diffs, `${skill.skillName}.casePassRate`, committedSkill.casePassRate, recomputed.casePassRate);
248
+ compareMetric(diffs, `${skill.skillName}.assertionPassRate`, committedSkill.assertionPassRate, recomputed.assertionPassRate);
249
+ compareMetric(diffs, `${skill.skillName}.triggerRecall`, committedSkill.triggerRecall, recomputed.triggerRecall);
250
+ compareMetric(diffs, `${skill.skillName}.triggerSpecificity`, committedSkill.triggerSpecificity, recomputed.triggerSpecificity);
251
+ compareMetric(diffs, `${skill.skillName}.balancedTriggerAccuracy`, committedSkill.balancedTriggerAccuracy, recomputed.balancedTriggerAccuracy);
252
+ } else {
253
+ compareMetric(diffs, `${skill.skillName}.baselinePassRate`, committedSkill.baselinePassRate, recomputed.baselinePassRate);
254
+ compareMetric(diffs, `${skill.skillName}.withSkillPassRate`, committedSkill.withSkillPassRate, recomputed.withSkillPassRate);
255
+ }
256
+ }
257
+ return diffs.length > 0 ? {
258
+ runId,
259
+ ok: false,
260
+ reason: "recomputed scores differ from committed results.json",
261
+ diffs
262
+ } : {
263
+ runId,
264
+ ok: true
265
+ };
266
+ }
267
+ function listEvalRuns(projectRoot) {
268
+ const runsDir = path.join(projectRoot, "benchmarks", "evals", "runs");
269
+ if (!fs_extra.default.existsSync(runsDir)) return [];
270
+ return fs_extra.default.readdirSync(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
271
+ }
272
+ function readEvalsReport(projectRoot) {
273
+ const reportPath = path.join(projectRoot, "evals-report.md");
274
+ return fs_extra.default.existsSync(reportPath) ? fs_extra.default.readFileSync(reportPath, "utf8") : null;
275
+ }
276
+ //#endregion
277
+ //#region src/commands/evals.ts
278
+ /**
279
+ * Command for verifying and displaying live skill-eval runs
280
+ * (see docs/EVALS.md and the `evals-run` workflow for how runs are produced).
281
+ */
282
+ var EvalsCommand = class {
283
+ async run(action, options = {}, projectRoot = process.cwd()) {
284
+ switch (action) {
285
+ case "verify": return this.verify(options.run, projectRoot);
286
+ case "report": return this.report(projectRoot);
287
+ default:
288
+ console.error(picocolors.default.red(`Unknown evals action "${action}". Use: verify | report`));
289
+ process.exit(1);
290
+ }
291
+ }
292
+ async verify(runId, projectRoot) {
293
+ const runIds = runId ? [runId] : listEvalRuns(projectRoot);
294
+ if (runIds.length === 0) {
295
+ console.log(picocolors.default.yellow("No eval runs found under benchmarks/evals/runs/. Run the `evals-run` workflow first (see docs/EVALS.md)."));
296
+ return;
297
+ }
298
+ let anyFailed = false;
299
+ for (const id of runIds) {
300
+ const outcome = verifyEvalRun(projectRoot, id);
301
+ if (outcome.ok) console.log(picocolors.default.green(`✅ ${id}: verified`));
302
+ else {
303
+ anyFailed = true;
304
+ console.log(picocolors.default.red(`❌ ${id}: ${outcome.reason}`));
305
+ for (const d of outcome.diffs || []) console.log(picocolors.default.red(` - ${d}`));
306
+ }
307
+ }
308
+ if (anyFailed) process.exit(1);
309
+ }
310
+ async report(projectRoot) {
311
+ const report = readEvalsReport(projectRoot);
312
+ if (!report) {
313
+ console.log(picocolors.default.yellow("No evals-report.md found at the project root yet. Run the `evals-run` workflow (see docs/EVALS.md), then `pnpm evals:report`."));
314
+ return;
315
+ }
316
+ console.log(report);
317
+ }
318
+ };
319
+ //#endregion
97
320
  //#region src/services/FeedbackService.ts
98
321
  /**
99
322
  * Service to handle automated feedback reporting via Proxy Backend.
@@ -746,8 +969,18 @@ var DEFAULT_WORKFLOWS = [
746
969
  "implement-feature",
747
970
  "verify-work",
748
971
  "verify-bug",
749
- "security-test"
972
+ "security-test",
973
+ "uat-signoff",
974
+ "incident-hotfix"
750
975
  ];
976
+ /**
977
+ * Workflows that must never be synced to a consumer project, regardless of
978
+ * `.skillsrc` (even `workflows: true`). These depend on agent-skills-standard's
979
+ * own repo-root tooling (e.g. `scripts/evals/*` and its `pnpm evals:*` scripts,
980
+ * `benchmarks/evals/`) which is never distributed to consumers — the workflow
981
+ * would be broken/non-functional outside this monorepo.
982
+ */
983
+ var INTERNAL_ONLY_WORKFLOWS = ["evals-run"];
751
984
  var getAgentDefinition = (id) => {
752
985
  switch (id) {
753
986
  case Agent.Cursor: return {
@@ -874,8 +1107,8 @@ var getAgentDefinition = (id) => {
874
1107
  ruleExtension: ".md",
875
1108
  frontmatterStyle: "cursor",
876
1109
  detectionFiles: [".windsurf"],
877
- workflowFormat: "none",
878
- workflowPath: "",
1110
+ workflowFormat: "native",
1111
+ workflowPath: ".windsurf/workflows",
879
1112
  hookScriptPath: ".windsurf/hooks/preedit-skill-loader.js",
880
1113
  hookConfigPath: ".windsurf/hooks.json"
881
1114
  };
@@ -3835,7 +4068,7 @@ var SkillSyncService = class {
3835
4068
  };
3836
4069
  //#endregion
3837
4070
  //#region src/services/utils/WorkflowTransformer.ts
3838
- var WORKFLOW_ARGUMENTS = "Optional args: slug=<feature>, ticket=<id/url>, mode=interactive|autonomous|channel, channel=<id>, auto_continue=true|false.";
4071
+ var WORKFLOW_ARGUMENTS = "Optional args: slug=<feature>, ticket=<id/url>, mode=interactive|autonomous|channel, channel=<id>, auto_continue=true|false, profile=business|hybrid|technical.";
3839
4072
  /**
3840
4073
  * Transforms workflow markdown into each agent's native user-invoked command format.
3841
4074
  *
@@ -3862,11 +4095,11 @@ var WorkflowTransformer = class {
3862
4095
  rawContent: source.content
3863
4096
  };
3864
4097
  }
3865
- static transform(source, format, workflowSourcePath = ".agents/workflows") {
4098
+ static transform(source, format) {
3866
4099
  const parsed = this.parse(source);
3867
- return this.transformParsed(parsed, format, workflowSourcePath);
4100
+ return this.transformParsed(parsed, format);
3868
4101
  }
3869
- static transformParsed(parsed, format, workflowSourcePath = ".agents/workflows") {
4102
+ static transformParsed(parsed, format) {
3870
4103
  if (format === "none") return null;
3871
4104
  const { description, body } = parsed;
3872
4105
  const baseName = parsed.key;
@@ -3881,7 +4114,7 @@ var WorkflowTransformer = class {
3881
4114
  };
3882
4115
  case "toml": return {
3883
4116
  name: `${baseName}.toml`,
3884
- content: this.toGeminiCommand(baseName, description, workflowSourcePath)
4117
+ content: this.toGeminiCommand(description, body)
3885
4118
  };
3886
4119
  case "prompt": return {
3887
4120
  name: `${baseName}.prompt.md`,
@@ -3929,17 +4162,18 @@ ${body}`;
3929
4162
  /**
3930
4163
  * Gemini CLI TOML command format.
3931
4164
  * Lives at .gemini/commands/<name>.toml
3932
- * References the workflow source file via prompt.
4165
+ * Inlines the workflow body so the command is self-contained even when
4166
+ * .agents/workflows/ is not also synced to this runtime.
3933
4167
  * User invokes via /<name> [arguments]
3934
4168
  */
3935
- static toGeminiCommand(name, description, workflowSourcePath) {
4169
+ static toGeminiCommand(description, body) {
3936
4170
  return `description = "${description.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"
3937
4171
  prompt = """
3938
- Please execute the workflow defined in \`${workflowSourcePath}/${name}.md\` for: {{args}}
4172
+ Execute this workflow for: {{args}}
3939
4173
 
3940
4174
  ${WORKFLOW_ARGUMENTS}
3941
4175
 
3942
- Follow the exact steps in the workflow file.
4176
+ ${body.replace(/"""/g, "\\\"\\\"\\\"")}
3943
4177
  """
3944
4178
  `;
3945
4179
  }
@@ -4005,7 +4239,7 @@ var WorkflowSyncService = class {
4005
4239
  const ref = (await this.githubService.getRepoInfo(owner, repo))?.default_branch || "main";
4006
4240
  const treeData = await this.githubService.getRepoTree(owner, repo, ref);
4007
4241
  if (!treeData) return false;
4008
- const availableWorkflows = treeData.tree.filter((f) => this.isWorkflowMarkdownPath(f.path)).map((f) => this.workflowNameFromPath(f.path));
4242
+ const availableWorkflows = treeData.tree.filter((f) => this.isWorkflowMarkdownPath(f.path)).map((f) => this.workflowNameFromPath(f.path)).filter((wf) => !INTERNAL_ONLY_WORKFLOWS.includes(wf));
4009
4243
  if (availableWorkflows.length === 0) return false;
4010
4244
  let changed = false;
4011
4245
  if (Array.isArray(config.workflows)) {
@@ -4044,6 +4278,7 @@ var WorkflowSyncService = class {
4044
4278
  }
4045
4279
  const workflowFiles = treeData.tree.filter((f) => {
4046
4280
  if (!this.isWorkflowMarkdownPath(f.path)) return false;
4281
+ if (INTERNAL_ONLY_WORKFLOWS.includes(this.workflowNameFromPath(f.path))) return false;
4047
4282
  if (typeof config.workflows === "boolean") return config.workflows;
4048
4283
  if (Array.isArray(config.workflows)) return config.workflows.includes(this.workflowNameFromPath(f.path));
4049
4284
  return false;
@@ -4086,8 +4321,6 @@ var WorkflowSyncService = class {
4086
4321
  if (!agentDef || agentDef.workflowFormat === "none") continue;
4087
4322
  const workflowDir = path.default.join(process.cwd(), agentDef.workflowPath);
4088
4323
  await fs_extra.default.ensureDir(workflowDir);
4089
- const sourceWorkflowDir = path.default.join(process.cwd(), ".agents/workflows");
4090
- const workflowSourceRelative = path.default.relative(workflowDir, sourceWorkflowDir);
4091
4324
  let written = 0;
4092
4325
  for (const wf of workflows) {
4093
4326
  if (wf.skill !== "workflows") continue;
@@ -4096,7 +4329,7 @@ var WorkflowSyncService = class {
4096
4329
  name: fileItem.name,
4097
4330
  content: fileItem.content
4098
4331
  });
4099
- const transformed = WorkflowTransformer.transformParsed(parsed, agentDef.workflowFormat, workflowSourceRelative);
4332
+ const transformed = WorkflowTransformer.transformParsed(parsed, agentDef.workflowFormat);
4100
4333
  if (!transformed) continue;
4101
4334
  let targetFilePath;
4102
4335
  if (agentDef.workflowFormat === "skill") {
@@ -5072,7 +5305,7 @@ var FrontmatterRule = class {
5072
5305
  if (descMatch && descMatch[1].length > 1024) {
5073
5306
  result.errors.push(`Description too long (${descMatch[1].length} chars > 1024 limit)`);
5074
5307
  result.passed = false;
5075
- }
5308
+ } else if (descMatch && descMatch[1].length > 300) result.warnings.push(`Description is ${descMatch[1].length} chars (> 300); trim for _INDEX.md readability`);
5076
5309
  }
5077
5310
  return result;
5078
5311
  }
@@ -5112,16 +5345,82 @@ var InstructionsStyleRule = class {
5112
5345
  }
5113
5346
  };
5114
5347
  /**
5115
- * Ensures the mandatory priority section is present.
5348
+ * Canonical priority label per tier, enforced by PriorityRule.
5349
+ */
5350
+ var CANONICAL_PRIORITY_LABELS = {
5351
+ P0: "CRITICAL",
5352
+ P1: "HIGH",
5353
+ P2: "MEDIUM",
5354
+ P3: "LOW"
5355
+ };
5356
+ /**
5357
+ * Ensures the mandatory priority section is present and, once `labelSeverity`
5358
+ * is 'error', that its label matches the canonical vocabulary.
5116
5359
  */
5117
5360
  var PriorityRule = class {
5118
5361
  name = "Priority Section";
5362
+ constructor(labelSeverity = "warning") {
5363
+ this.labelSeverity = labelSeverity;
5364
+ }
5119
5365
  async validate(content) {
5120
- if (!content.includes("## **Priority:")) return {
5366
+ const result = {
5367
+ passed: true,
5368
+ errors: [],
5369
+ warnings: []
5370
+ };
5371
+ const match = content.match(/^## \*\*Priority:\s*(P\d)(?:\s*\(([^)]*)\))?([^\n]*)\*\*/m);
5372
+ if (!match) return {
5121
5373
  passed: false,
5122
5374
  errors: ["Missing priority section"],
5123
5375
  warnings: []
5124
5376
  };
5377
+ const [, tier, label, trailing] = match;
5378
+ const canonicalLabel = CANONICAL_PRIORITY_LABELS[tier];
5379
+ if (!canonicalLabel) {
5380
+ result.errors.push(`Unknown priority tier "${tier}"`);
5381
+ result.passed = false;
5382
+ return result;
5383
+ }
5384
+ if (!(label === canonicalLabel && !trailing?.trim())) {
5385
+ const message = `Priority label must be "${tier} (${canonicalLabel})"`;
5386
+ if (this.labelSeverity === "error") {
5387
+ result.errors.push(message);
5388
+ result.passed = false;
5389
+ } else result.warnings.push(message);
5390
+ }
5391
+ return result;
5392
+ }
5393
+ };
5394
+ /**
5395
+ * Ensures every skill is routable: at least one file glob or keyword trigger.
5396
+ */
5397
+ var TriggersRule = class {
5398
+ name = "Triggers";
5399
+ async validate(content) {
5400
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
5401
+ if (!frontmatterMatch) return {
5402
+ passed: true,
5403
+ errors: [],
5404
+ warnings: []
5405
+ };
5406
+ let parsed;
5407
+ try {
5408
+ parsed = js_yaml.default.load(frontmatterMatch[1]);
5409
+ } catch {
5410
+ return {
5411
+ passed: false,
5412
+ errors: ["Invalid YAML frontmatter"],
5413
+ warnings: []
5414
+ };
5415
+ }
5416
+ const triggers = parsed?.metadata?.triggers;
5417
+ const files = Array.isArray(triggers?.files) ? triggers.files : [];
5418
+ const keywords = Array.isArray(triggers?.keywords) ? triggers.keywords : [];
5419
+ if (files.length === 0 && keywords.length === 0) return {
5420
+ passed: false,
5421
+ errors: ["Skill is unroutable: metadata.triggers must define at least one files glob or keywords entry"],
5422
+ warnings: []
5423
+ };
5125
5424
  return {
5126
5425
  passed: true,
5127
5426
  errors: [],
@@ -5173,7 +5472,8 @@ var SkillValidator = class {
5173
5472
  new SizeRule(),
5174
5473
  new FrontmatterRule(),
5175
5474
  new InstructionsStyleRule(),
5176
- new PriorityRule(),
5475
+ new PriorityRule("error"),
5476
+ new TriggersRule(),
5177
5477
  new DirectoryStructureRule()
5178
5478
  ];
5179
5479
  /**
@@ -5329,6 +5629,9 @@ program.command("mcp <action> [scope]").description("Manage the optional MCP ser
5329
5629
  program.command("hooks <action>").description("Manage the PreToolUse skill-loader hook. Actions: status | install | uninstall").action(async (action) => {
5330
5630
  await new HooksCommand().run(action);
5331
5631
  });
5632
+ program.command("evals <action>").description("Verify or display live skill-eval runs. Actions: verify | report").option("--run <runId>", "For verify: check a single run id instead of all committed runs").action(async (action, opts) => {
5633
+ await new EvalsCommand().run(action, opts);
5634
+ });
5332
5635
  program.parse();
5333
5636
  //#endregion
5334
5637