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 +1 -1
- package/dist/index.js +328 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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: "
|
|
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
|
|
4098
|
+
static transform(source, format) {
|
|
3866
4099
|
const parsed = this.parse(source);
|
|
3867
|
-
return this.transformParsed(parsed, format
|
|
4100
|
+
return this.transformParsed(parsed, format);
|
|
3868
4101
|
}
|
|
3869
|
-
static transformParsed(parsed, format
|
|
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(
|
|
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
|
-
*
|
|
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(
|
|
4169
|
+
static toGeminiCommand(description, body) {
|
|
3936
4170
|
return `description = "${description.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"
|
|
3937
4171
|
prompt = """
|
|
3938
|
-
|
|
4172
|
+
Execute this workflow for: {{args}}
|
|
3939
4173
|
|
|
3940
4174
|
${WORKFLOW_ARGUMENTS}
|
|
3941
4175
|
|
|
3942
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|