agent-skills-standard 2.5.0 → 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 +2 -2
- package/dist/index.js +366 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
**Sync 242 AI coding standards to any project in one command.** Works with Cursor, Claude Code, GitHub Copilot, Gemini, Windsurf, Trae, Kiro, and Roo.
|
|
7
7
|
|
|
8
|
-
**Current release:** `v2.
|
|
8
|
+
**Current release:** `v2.5.1` — automated SkillSpector security scanning, pull request evaluation gates, and verified release tags.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npx agent-skills-standard@latest init # detect your stack
|
|
@@ -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.
|
|
@@ -342,6 +565,7 @@ var Framework = /* @__PURE__ */ function(Framework) {
|
|
|
342
565
|
Framework["Android"] = "android";
|
|
343
566
|
Framework["iOS"] = "ios";
|
|
344
567
|
Framework["Laravel"] = "laravel";
|
|
568
|
+
Framework["Python"] = "python";
|
|
345
569
|
return Framework;
|
|
346
570
|
}({});
|
|
347
571
|
//#endregion
|
|
@@ -672,7 +896,8 @@ var BACKEND_FRAMEWORKS = [
|
|
|
672
896
|
Framework.NestJS,
|
|
673
897
|
Framework.Golang,
|
|
674
898
|
Framework.SpringBoot,
|
|
675
|
-
Framework.Laravel
|
|
899
|
+
Framework.Laravel,
|
|
900
|
+
Framework.Python
|
|
676
901
|
];
|
|
677
902
|
var FRONTEND_FRAMEWORKS = [
|
|
678
903
|
Framework.React,
|
|
@@ -744,8 +969,18 @@ var DEFAULT_WORKFLOWS = [
|
|
|
744
969
|
"implement-feature",
|
|
745
970
|
"verify-work",
|
|
746
971
|
"verify-bug",
|
|
747
|
-
"security-test"
|
|
972
|
+
"security-test",
|
|
973
|
+
"uat-signoff",
|
|
974
|
+
"incident-hotfix"
|
|
748
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"];
|
|
749
984
|
var getAgentDefinition = (id) => {
|
|
750
985
|
switch (id) {
|
|
751
986
|
case Agent.Cursor: return {
|
|
@@ -872,8 +1107,8 @@ var getAgentDefinition = (id) => {
|
|
|
872
1107
|
ruleExtension: ".md",
|
|
873
1108
|
frontmatterStyle: "cursor",
|
|
874
1109
|
detectionFiles: [".windsurf"],
|
|
875
|
-
workflowFormat: "
|
|
876
|
-
workflowPath: "",
|
|
1110
|
+
workflowFormat: "native",
|
|
1111
|
+
workflowPath: ".windsurf/workflows",
|
|
877
1112
|
hookScriptPath: ".windsurf/hooks/preedit-skill-loader.js",
|
|
878
1113
|
hookConfigPath: ".windsurf/hooks.json"
|
|
879
1114
|
};
|
|
@@ -1007,6 +1242,20 @@ var getFrameworkDefinition = (id) => {
|
|
|
1007
1242
|
javascript: ["resources/js", "vite.config.js"]
|
|
1008
1243
|
}
|
|
1009
1244
|
};
|
|
1245
|
+
case Framework.Python: return {
|
|
1246
|
+
id,
|
|
1247
|
+
name: "Python",
|
|
1248
|
+
languages: ["python"],
|
|
1249
|
+
detectionFiles: [
|
|
1250
|
+
"pyproject.toml",
|
|
1251
|
+
"requirements.txt",
|
|
1252
|
+
"setup.py",
|
|
1253
|
+
"setup.cfg",
|
|
1254
|
+
"Pipfile",
|
|
1255
|
+
"poetry.lock",
|
|
1256
|
+
"uv.lock"
|
|
1257
|
+
]
|
|
1258
|
+
};
|
|
1010
1259
|
}
|
|
1011
1260
|
};
|
|
1012
1261
|
var SUPPORTED_AGENTS = Object.values(Agent).map(getAgentDefinition);
|
|
@@ -3819,7 +4068,7 @@ var SkillSyncService = class {
|
|
|
3819
4068
|
};
|
|
3820
4069
|
//#endregion
|
|
3821
4070
|
//#region src/services/utils/WorkflowTransformer.ts
|
|
3822
|
-
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.";
|
|
3823
4072
|
/**
|
|
3824
4073
|
* Transforms workflow markdown into each agent's native user-invoked command format.
|
|
3825
4074
|
*
|
|
@@ -3846,11 +4095,11 @@ var WorkflowTransformer = class {
|
|
|
3846
4095
|
rawContent: source.content
|
|
3847
4096
|
};
|
|
3848
4097
|
}
|
|
3849
|
-
static transform(source, format
|
|
4098
|
+
static transform(source, format) {
|
|
3850
4099
|
const parsed = this.parse(source);
|
|
3851
|
-
return this.transformParsed(parsed, format
|
|
4100
|
+
return this.transformParsed(parsed, format);
|
|
3852
4101
|
}
|
|
3853
|
-
static transformParsed(parsed, format
|
|
4102
|
+
static transformParsed(parsed, format) {
|
|
3854
4103
|
if (format === "none") return null;
|
|
3855
4104
|
const { description, body } = parsed;
|
|
3856
4105
|
const baseName = parsed.key;
|
|
@@ -3865,7 +4114,7 @@ var WorkflowTransformer = class {
|
|
|
3865
4114
|
};
|
|
3866
4115
|
case "toml": return {
|
|
3867
4116
|
name: `${baseName}.toml`,
|
|
3868
|
-
content: this.toGeminiCommand(
|
|
4117
|
+
content: this.toGeminiCommand(description, body)
|
|
3869
4118
|
};
|
|
3870
4119
|
case "prompt": return {
|
|
3871
4120
|
name: `${baseName}.prompt.md`,
|
|
@@ -3913,17 +4162,18 @@ ${body}`;
|
|
|
3913
4162
|
/**
|
|
3914
4163
|
* Gemini CLI TOML command format.
|
|
3915
4164
|
* Lives at .gemini/commands/<name>.toml
|
|
3916
|
-
*
|
|
4165
|
+
* Inlines the workflow body so the command is self-contained even when
|
|
4166
|
+
* .agents/workflows/ is not also synced to this runtime.
|
|
3917
4167
|
* User invokes via /<name> [arguments]
|
|
3918
4168
|
*/
|
|
3919
|
-
static toGeminiCommand(
|
|
4169
|
+
static toGeminiCommand(description, body) {
|
|
3920
4170
|
return `description = "${description.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"
|
|
3921
4171
|
prompt = """
|
|
3922
|
-
|
|
4172
|
+
Execute this workflow for: {{args}}
|
|
3923
4173
|
|
|
3924
4174
|
${WORKFLOW_ARGUMENTS}
|
|
3925
4175
|
|
|
3926
|
-
|
|
4176
|
+
${body.replace(/"""/g, "\\\"\\\"\\\"")}
|
|
3927
4177
|
"""
|
|
3928
4178
|
`;
|
|
3929
4179
|
}
|
|
@@ -3989,7 +4239,7 @@ var WorkflowSyncService = class {
|
|
|
3989
4239
|
const ref = (await this.githubService.getRepoInfo(owner, repo))?.default_branch || "main";
|
|
3990
4240
|
const treeData = await this.githubService.getRepoTree(owner, repo, ref);
|
|
3991
4241
|
if (!treeData) return false;
|
|
3992
|
-
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));
|
|
3993
4243
|
if (availableWorkflows.length === 0) return false;
|
|
3994
4244
|
let changed = false;
|
|
3995
4245
|
if (Array.isArray(config.workflows)) {
|
|
@@ -4028,6 +4278,7 @@ var WorkflowSyncService = class {
|
|
|
4028
4278
|
}
|
|
4029
4279
|
const workflowFiles = treeData.tree.filter((f) => {
|
|
4030
4280
|
if (!this.isWorkflowMarkdownPath(f.path)) return false;
|
|
4281
|
+
if (INTERNAL_ONLY_WORKFLOWS.includes(this.workflowNameFromPath(f.path))) return false;
|
|
4031
4282
|
if (typeof config.workflows === "boolean") return config.workflows;
|
|
4032
4283
|
if (Array.isArray(config.workflows)) return config.workflows.includes(this.workflowNameFromPath(f.path));
|
|
4033
4284
|
return false;
|
|
@@ -4070,8 +4321,6 @@ var WorkflowSyncService = class {
|
|
|
4070
4321
|
if (!agentDef || agentDef.workflowFormat === "none") continue;
|
|
4071
4322
|
const workflowDir = path.default.join(process.cwd(), agentDef.workflowPath);
|
|
4072
4323
|
await fs_extra.default.ensureDir(workflowDir);
|
|
4073
|
-
const sourceWorkflowDir = path.default.join(process.cwd(), ".agents/workflows");
|
|
4074
|
-
const workflowSourceRelative = path.default.relative(workflowDir, sourceWorkflowDir);
|
|
4075
4324
|
let written = 0;
|
|
4076
4325
|
for (const wf of workflows) {
|
|
4077
4326
|
if (wf.skill !== "workflows") continue;
|
|
@@ -4080,7 +4329,7 @@ var WorkflowSyncService = class {
|
|
|
4080
4329
|
name: fileItem.name,
|
|
4081
4330
|
content: fileItem.content
|
|
4082
4331
|
});
|
|
4083
|
-
const transformed = WorkflowTransformer.transformParsed(parsed, agentDef.workflowFormat
|
|
4332
|
+
const transformed = WorkflowTransformer.transformParsed(parsed, agentDef.workflowFormat);
|
|
4084
4333
|
if (!transformed) continue;
|
|
4085
4334
|
let targetFilePath;
|
|
4086
4335
|
if (agentDef.workflowFormat === "skill") {
|
|
@@ -4361,25 +4610,35 @@ var MarkdownUtils = class {
|
|
|
4361
4610
|
* @param rootDir Project root directory
|
|
4362
4611
|
* @param targets Array of target file names
|
|
4363
4612
|
* @param indexContent The markdown content to inject
|
|
4613
|
+
* @returns Array of target files that were successfully updated/created
|
|
4364
4614
|
*/
|
|
4365
4615
|
static async injectIndex(rootDir, targets, indexContent) {
|
|
4616
|
+
const updated = [];
|
|
4366
4617
|
for (const target of targets) {
|
|
4367
4618
|
const targetPath = path.default.join(rootDir, target);
|
|
4368
4619
|
let content = "";
|
|
4620
|
+
const markerStart = "<!-- SKILLS_INDEX_START -->";
|
|
4621
|
+
const markerEnd = "<!-- SKILLS_INDEX_END -->";
|
|
4369
4622
|
if (await fs_extra.default.pathExists(targetPath)) {
|
|
4370
4623
|
content = await fs_extra.default.readFile(targetPath, "utf8");
|
|
4371
|
-
const markerStart = "<!-- SKILLS_INDEX_START -->";
|
|
4372
|
-
const markerEnd = "<!-- SKILLS_INDEX_END -->";
|
|
4373
4624
|
const startIndex = content.indexOf(markerStart);
|
|
4374
4625
|
const endIndex = content.indexOf(markerEnd);
|
|
4375
4626
|
if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) content = `${content.substring(0, startIndex + 27)}\n${indexContent}\n${content.substring(endIndex)}`;
|
|
4376
|
-
else
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4627
|
+
else continue;
|
|
4628
|
+
} else content = [
|
|
4629
|
+
"# Project Context for AI Agents",
|
|
4630
|
+
"",
|
|
4631
|
+
"This file provides context and instructions for AI agents working in this repository.",
|
|
4632
|
+
"",
|
|
4633
|
+
markerStart,
|
|
4634
|
+
indexContent,
|
|
4635
|
+
markerEnd,
|
|
4636
|
+
""
|
|
4637
|
+
].join("\n");
|
|
4381
4638
|
await fs_extra.default.outputFile(targetPath, content);
|
|
4639
|
+
updated.push(target);
|
|
4382
4640
|
}
|
|
4641
|
+
return updated;
|
|
4383
4642
|
}
|
|
4384
4643
|
};
|
|
4385
4644
|
//#endregion
|
|
@@ -4475,13 +4734,11 @@ var SyncService = class {
|
|
|
4475
4734
|
if (Object.keys(categoryIndices).length > 0) console.log(picocolors.default.green(` ✅ Generated _INDEX.md for ${Object.keys(categoryIndices).length} categories.`));
|
|
4476
4735
|
const mcpEnabled = config.mcp?.enabled === true;
|
|
4477
4736
|
const routerIndex = await generator.assembleRouterIndex(baseDir, allowedCategories, mcpEnabled);
|
|
4478
|
-
await MarkdownUtils.injectIndex(process.cwd(), ["AGENTS.md"], routerIndex);
|
|
4479
|
-
console.log(picocolors.default.
|
|
4737
|
+
if ((await MarkdownUtils.injectIndex(process.cwd(), ["AGENTS.md"], routerIndex)).length > 0) console.log(picocolors.default.green(" ✅ AGENTS.md router index updated."));
|
|
4738
|
+
else console.log(picocolors.default.yellow(" ⚠️ Skipped AGENTS.md update: index markers <!-- SKILLS_INDEX_START --> … <!-- SKILLS_INDEX_END --> are missing or out of order. Opt-in by adding these markers to your file."));
|
|
4480
4739
|
const serverDir = path.default.join(process.cwd(), "server");
|
|
4481
|
-
if (await fs_extra.default.pathExists(serverDir))
|
|
4482
|
-
|
|
4483
|
-
console.log(picocolors.default.green(" ✅ server/AGENTS.md router index updated."));
|
|
4484
|
-
}
|
|
4740
|
+
if (await fs_extra.default.pathExists(serverDir)) if ((await MarkdownUtils.injectIndex(serverDir, ["AGENTS.md"], routerIndex)).length > 0) console.log(picocolors.default.green(" ✅ server/AGENTS.md router index updated."));
|
|
4741
|
+
else console.log(picocolors.default.yellow(" ⚠️ Skipped server/AGENTS.md update: index markers <!-- SKILLS_INDEX_START --> … <!-- SKILLS_INDEX_END --> are missing or out of order in server/AGENTS.md. Opt-in via markers."));
|
|
4485
4742
|
await new AgentBridgeService().bridge(process.cwd(), agents);
|
|
4486
4743
|
} catch (error) {
|
|
4487
4744
|
console.log(picocolors.default.yellow(` ⚠️ Failed to update index: ${error}`));
|
|
@@ -5048,7 +5305,7 @@ var FrontmatterRule = class {
|
|
|
5048
5305
|
if (descMatch && descMatch[1].length > 1024) {
|
|
5049
5306
|
result.errors.push(`Description too long (${descMatch[1].length} chars > 1024 limit)`);
|
|
5050
5307
|
result.passed = false;
|
|
5051
|
-
}
|
|
5308
|
+
} else if (descMatch && descMatch[1].length > 300) result.warnings.push(`Description is ${descMatch[1].length} chars (> 300); trim for _INDEX.md readability`);
|
|
5052
5309
|
}
|
|
5053
5310
|
return result;
|
|
5054
5311
|
}
|
|
@@ -5088,16 +5345,82 @@ var InstructionsStyleRule = class {
|
|
|
5088
5345
|
}
|
|
5089
5346
|
};
|
|
5090
5347
|
/**
|
|
5091
|
-
*
|
|
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.
|
|
5092
5359
|
*/
|
|
5093
5360
|
var PriorityRule = class {
|
|
5094
5361
|
name = "Priority Section";
|
|
5362
|
+
constructor(labelSeverity = "warning") {
|
|
5363
|
+
this.labelSeverity = labelSeverity;
|
|
5364
|
+
}
|
|
5095
5365
|
async validate(content) {
|
|
5096
|
-
|
|
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 {
|
|
5097
5373
|
passed: false,
|
|
5098
5374
|
errors: ["Missing priority section"],
|
|
5099
5375
|
warnings: []
|
|
5100
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
|
+
};
|
|
5101
5424
|
return {
|
|
5102
5425
|
passed: true,
|
|
5103
5426
|
errors: [],
|
|
@@ -5149,7 +5472,8 @@ var SkillValidator = class {
|
|
|
5149
5472
|
new SizeRule(),
|
|
5150
5473
|
new FrontmatterRule(),
|
|
5151
5474
|
new InstructionsStyleRule(),
|
|
5152
|
-
new PriorityRule(),
|
|
5475
|
+
new PriorityRule("error"),
|
|
5476
|
+
new TriggersRule(),
|
|
5153
5477
|
new DirectoryStructureRule()
|
|
5154
5478
|
];
|
|
5155
5479
|
/**
|
|
@@ -5305,6 +5629,9 @@ program.command("mcp <action> [scope]").description("Manage the optional MCP ser
|
|
|
5305
5629
|
program.command("hooks <action>").description("Manage the PreToolUse skill-loader hook. Actions: status | install | uninstall").action(async (action) => {
|
|
5306
5630
|
await new HooksCommand().run(action);
|
|
5307
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
|
+
});
|
|
5308
5635
|
program.parse();
|
|
5309
5636
|
//#endregion
|
|
5310
5637
|
|