agent-skills-standard 2.2.5 → 2.4.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 +282 -187
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -268,7 +268,7 @@ var Agent = /* @__PURE__ */ function(Agent) {
|
|
|
268
268
|
Agent["Claude"] = "claude";
|
|
269
269
|
Agent["Copilot"] = "copilot";
|
|
270
270
|
Agent["Antigravity"] = "antigravity";
|
|
271
|
-
Agent["
|
|
271
|
+
Agent["Codex"] = "codex";
|
|
272
272
|
Agent["OpenCode"] = "opencode";
|
|
273
273
|
Agent["Gemini"] = "gemini";
|
|
274
274
|
Agent["Roo"] = "roo";
|
|
@@ -671,13 +671,24 @@ function getFrameworkType(framework) {
|
|
|
671
671
|
}
|
|
672
672
|
var DEFAULT_REGISTER = "https://github.com/HoangNguyen0403/agent-skills-standard";
|
|
673
673
|
var DEFAULT_WORKFLOWS = [
|
|
674
|
+
"sdlc",
|
|
675
|
+
"brainstorm-feature",
|
|
674
676
|
"code-review",
|
|
675
677
|
"codebase-review",
|
|
678
|
+
"design-solution",
|
|
679
|
+
"deploy-release",
|
|
676
680
|
"plan-feature",
|
|
681
|
+
"implementation-readiness",
|
|
682
|
+
"review-ticket",
|
|
683
|
+
"traceability-audit",
|
|
684
|
+
"session-report",
|
|
685
|
+
"publish-notes",
|
|
686
|
+
"retro-learn",
|
|
677
687
|
"skill-benchmark",
|
|
678
688
|
"pentest",
|
|
679
689
|
"dev-fix",
|
|
680
690
|
"implement-feature",
|
|
691
|
+
"verify-work",
|
|
681
692
|
"verify-bug"
|
|
682
693
|
];
|
|
683
694
|
var getAgentDefinition = (id) => {
|
|
@@ -692,7 +703,9 @@ var getAgentDefinition = (id) => {
|
|
|
692
703
|
detectionFiles: [".cursor", ".cursorrules"],
|
|
693
704
|
workflowFormat: "skill",
|
|
694
705
|
workflowPath: ".cursor/skills",
|
|
695
|
-
agentPath: ".cursor/agents"
|
|
706
|
+
agentPath: ".cursor/agents",
|
|
707
|
+
hookScriptPath: ".cursor/hooks/preedit-skill-loader.js",
|
|
708
|
+
hookConfigPath: ".cursor/hooks.json"
|
|
696
709
|
};
|
|
697
710
|
case Agent.Trae: return {
|
|
698
711
|
id,
|
|
@@ -716,7 +729,9 @@ var getAgentDefinition = (id) => {
|
|
|
716
729
|
detectionFiles: [".claude"],
|
|
717
730
|
workflowFormat: "command",
|
|
718
731
|
workflowPath: ".claude/commands",
|
|
719
|
-
agentPath: ".claude/agents"
|
|
732
|
+
agentPath: ".claude/agents",
|
|
733
|
+
hookScriptPath: ".claude/hooks/preedit-skill-loader.js",
|
|
734
|
+
hookConfigPath: ".claude/settings.json"
|
|
720
735
|
};
|
|
721
736
|
case Agent.Copilot: return {
|
|
722
737
|
id,
|
|
@@ -728,7 +743,9 @@ var getAgentDefinition = (id) => {
|
|
|
728
743
|
detectionFiles: [".github"],
|
|
729
744
|
workflowFormat: "prompt",
|
|
730
745
|
workflowPath: ".github/prompts",
|
|
731
|
-
agentPath: ".github/copilot-agents"
|
|
746
|
+
agentPath: ".github/copilot-agents",
|
|
747
|
+
hookScriptPath: ".github/hooks/preedit-skill-loader.js",
|
|
748
|
+
hookConfigPath: ".github/hooks.json"
|
|
732
749
|
};
|
|
733
750
|
case Agent.Antigravity: return {
|
|
734
751
|
id,
|
|
@@ -741,9 +758,9 @@ var getAgentDefinition = (id) => {
|
|
|
741
758
|
workflowFormat: "native",
|
|
742
759
|
workflowPath: ".agents/workflows"
|
|
743
760
|
};
|
|
744
|
-
case Agent.
|
|
761
|
+
case Agent.Codex: return {
|
|
745
762
|
id,
|
|
746
|
-
name: "
|
|
763
|
+
name: "Codex",
|
|
747
764
|
path: ".codex/skills",
|
|
748
765
|
ruleFile: ".codex/rules",
|
|
749
766
|
ruleExtension: ".md",
|
|
@@ -751,7 +768,9 @@ var getAgentDefinition = (id) => {
|
|
|
751
768
|
detectionFiles: [".codex"],
|
|
752
769
|
workflowFormat: "skill",
|
|
753
770
|
workflowPath: ".codex/skills",
|
|
754
|
-
agentPath: ".codex/agents"
|
|
771
|
+
agentPath: ".codex/agents",
|
|
772
|
+
hookScriptPath: ".codex/hooks/preedit-skill-loader.js",
|
|
773
|
+
hookConfigPath: ".codex/hooks.json"
|
|
755
774
|
};
|
|
756
775
|
case Agent.OpenCode: return {
|
|
757
776
|
id,
|
|
@@ -775,7 +794,9 @@ var getAgentDefinition = (id) => {
|
|
|
775
794
|
detectionFiles: [".gemini"],
|
|
776
795
|
workflowFormat: "toml",
|
|
777
796
|
workflowPath: ".gemini/commands",
|
|
778
|
-
agentPath: ".gemini/agents"
|
|
797
|
+
agentPath: ".gemini/agents",
|
|
798
|
+
hookScriptPath: ".gemini/hooks/preedit-skill-loader.js",
|
|
799
|
+
hookConfigPath: ".gemini/hooks.json"
|
|
779
800
|
};
|
|
780
801
|
case Agent.Roo: return {
|
|
781
802
|
id,
|
|
@@ -797,7 +818,9 @@ var getAgentDefinition = (id) => {
|
|
|
797
818
|
frontmatterStyle: "cursor",
|
|
798
819
|
detectionFiles: [".windsurf"],
|
|
799
820
|
workflowFormat: "none",
|
|
800
|
-
workflowPath: ""
|
|
821
|
+
workflowPath: "",
|
|
822
|
+
hookScriptPath: ".windsurf/hooks/preedit-skill-loader.js",
|
|
823
|
+
hookConfigPath: ".windsurf/hooks.json"
|
|
801
824
|
};
|
|
802
825
|
case Agent.Kiro: return {
|
|
803
826
|
id,
|
|
@@ -809,7 +832,8 @@ var getAgentDefinition = (id) => {
|
|
|
809
832
|
detectionFiles: [".kiro"],
|
|
810
833
|
workflowFormat: "native",
|
|
811
834
|
workflowPath: ".agents/workflows",
|
|
812
|
-
agentPath: ".kiro/agents"
|
|
835
|
+
agentPath: ".kiro/agents",
|
|
836
|
+
hookScriptPath: ".kiro/hooks/ags-skill-loader.md"
|
|
813
837
|
};
|
|
814
838
|
}
|
|
815
839
|
};
|
|
@@ -959,11 +983,17 @@ var SkillConfigSchema = zod.z.object({
|
|
|
959
983
|
workflows: zod.z.union([zod.z.boolean(), zod.z.array(zod.z.string())]).optional(),
|
|
960
984
|
mcp: McpConfigSchema.optional()
|
|
961
985
|
});
|
|
986
|
+
var DEFAULT_SDLC_SUPPORT_CATEGORIES = ["quality-engineering"];
|
|
962
987
|
/**
|
|
963
988
|
* Service for managing the `.skillsrc` configuration file.
|
|
964
989
|
* Handles loading, saving, and initial construction of the configuration based on project metadata.
|
|
965
990
|
*/
|
|
966
991
|
var ConfigService = class {
|
|
992
|
+
getCategoryRef(category, metadata) {
|
|
993
|
+
const categoryMetadata = metadata.categories?.[category];
|
|
994
|
+
if (!categoryMetadata?.version) return "main";
|
|
995
|
+
return `${categoryMetadata.tag_prefix || ""}${categoryMetadata.version}`;
|
|
996
|
+
}
|
|
967
997
|
/**
|
|
968
998
|
* Loads and validates the skill configuration from the workspace.
|
|
969
999
|
* @param cwd Current working directory
|
|
@@ -1015,6 +1045,7 @@ var ConfigService = class {
|
|
|
1015
1045
|
...commonExcludes.length > 0 && { exclude: commonExcludes }
|
|
1016
1046
|
};
|
|
1017
1047
|
}
|
|
1048
|
+
for (const category of DEFAULT_SDLC_SUPPORT_CATEGORIES) if (metadata.categories?.[category] && !skills[category]) skills[category] = { ref: this.getCategoryRef(category, metadata) };
|
|
1018
1049
|
if (BACKEND_FRAMEWORKS.includes(framework) && metadata.categories?.["database"]) skills["database"] = { ref: metadata.categories["database"].version ? `${metadata.categories["database"].tag_prefix || ""}${metadata.categories["database"].version}` : "main" };
|
|
1019
1050
|
return {
|
|
1020
1051
|
registry,
|
|
@@ -1124,84 +1155,75 @@ var ConfigService = class {
|
|
|
1124
1155
|
//#endregion
|
|
1125
1156
|
//#region src/services/HookService.ts
|
|
1126
1157
|
/**
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
* Always exits 0
|
|
1158
|
+
* Universal PreToolUse hook script (Node.js / JavaScript).
|
|
1159
|
+
* Reminds AI agents to call load_skills_for_files() before any code edit.
|
|
1160
|
+
* Guaranteed to execute on any machine running agent-skills-standard.
|
|
1161
|
+
* Always exits 0 to never block work.
|
|
1131
1162
|
*/
|
|
1132
|
-
var
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
Installed by agent-skills-standard (ags sync). Remove via: ags hooks uninstall
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
A hook fires mechanically on every edit - 100% trigger rate, cannot be forgotten.
|
|
1141
|
-
|
|
1142
|
-
Decision contract:
|
|
1143
|
-
- stdout: short MCP trigger prompt (injected as prompt context)
|
|
1144
|
-
- exit 0 always - never blocks the edit, only adds context
|
|
1145
|
-
- Silent on any error - hook bugs must never block legitimate work
|
|
1146
|
-
"""
|
|
1147
|
-
from __future__ import annotations
|
|
1148
|
-
|
|
1149
|
-
import json
|
|
1150
|
-
import os
|
|
1151
|
-
import sys
|
|
1152
|
-
from pathlib import Path
|
|
1153
|
-
|
|
1154
|
-
REPO_ROOT = Path(os.environ.get("CLAUDE_PROJECT_DIR") or Path(__file__).resolve().parents[2])
|
|
1155
|
-
|
|
1156
|
-
EDIT_TOOLS = {"Edit", "Write", "MultiEdit", "NotebookEdit"}
|
|
1157
|
-
|
|
1158
|
-
_SKIP_DIRS = (
|
|
1159
|
-
str(Path(os.path.realpath(REPO_ROOT / ".claude"))),
|
|
1160
|
-
str(Path(os.path.realpath(REPO_ROOT / ".gemini"))),
|
|
1161
|
-
)
|
|
1162
|
-
_SKIP_FILES = (
|
|
1163
|
-
str(Path(os.path.realpath(REPO_ROOT / "AGENTS.md"))),
|
|
1164
|
-
str(Path(os.path.realpath(REPO_ROOT / "CLAUDE.md"))),
|
|
1165
|
-
)
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
def _should_skip(file_path: str) -> bool:
|
|
1169
|
-
try:
|
|
1170
|
-
real = str(Path(os.path.realpath(file_path)))
|
|
1171
|
-
except Exception:
|
|
1172
|
-
return False
|
|
1173
|
-
if any(real.startswith(d) for d in _SKIP_DIRS):
|
|
1174
|
-
return True
|
|
1175
|
-
if real in _SKIP_FILES:
|
|
1176
|
-
return True
|
|
1177
|
-
return False
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
def main() -> None:
|
|
1181
|
-
try:
|
|
1182
|
-
data = json.load(sys.stdin)
|
|
1183
|
-
except Exception:
|
|
1184
|
-
sys.exit(0)
|
|
1185
|
-
|
|
1186
|
-
if data.get("tool_name") not in EDIT_TOOLS:
|
|
1187
|
-
sys.exit(0)
|
|
1163
|
+
var UNIVERSAL_SKILL_LOADER_JS = `#!/usr/bin/env node
|
|
1164
|
+
/**
|
|
1165
|
+
* PreToolUse hook: remind AI agent to call load_skills_for_files() before any code edit.
|
|
1166
|
+
* Installed by agent-skills-standard (ags sync). Remove via: ags hooks uninstall
|
|
1167
|
+
* Guaranteed to execute on any system with Node.js. Always exits 0 to never block work.
|
|
1168
|
+
*/
|
|
1169
|
+
const fs = require('fs');
|
|
1170
|
+
const path = require('path');
|
|
1188
1171
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1172
|
+
const REPO_ROOT = process.env.CLAUDE_PROJECT_DIR || path.resolve(__dirname, '../../');
|
|
1173
|
+
const EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
1174
|
+
const SKIP_DIRS = [
|
|
1175
|
+
path.resolve(REPO_ROOT, '.claude'),
|
|
1176
|
+
path.resolve(REPO_ROOT, '.gemini'),
|
|
1177
|
+
path.resolve(REPO_ROOT, '.codex'),
|
|
1178
|
+
path.resolve(REPO_ROOT, '.github'),
|
|
1179
|
+
path.resolve(REPO_ROOT, '.cursor'),
|
|
1180
|
+
path.resolve(REPO_ROOT, '.roo'),
|
|
1181
|
+
path.resolve(REPO_ROOT, '.trae'),
|
|
1182
|
+
path.resolve(REPO_ROOT, '.opencode'),
|
|
1183
|
+
path.resolve(REPO_ROOT, '.kiro'),
|
|
1184
|
+
path.resolve(REPO_ROOT, '.windsurf'),
|
|
1185
|
+
path.resolve(REPO_ROOT, '.agents'),
|
|
1186
|
+
path.resolve(REPO_ROOT, '.vscode'),
|
|
1187
|
+
];
|
|
1188
|
+
const SKIP_FILES = [
|
|
1189
|
+
path.resolve(REPO_ROOT, 'AGENTS.md'),
|
|
1190
|
+
path.resolve(REPO_ROOT, 'CLAUDE.md'),
|
|
1191
|
+
];
|
|
1192
1192
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1193
|
+
function shouldSkip(filePath) {
|
|
1194
|
+
try {
|
|
1195
|
+
const real = path.resolve(filePath);
|
|
1196
|
+
if (SKIP_DIRS.some(d => real.startsWith(d))) return true;
|
|
1197
|
+
if (SKIP_FILES.includes(real)) return true;
|
|
1198
|
+
return false;
|
|
1199
|
+
} catch {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1199
1203
|
|
|
1200
|
-
|
|
1204
|
+
let input = '';
|
|
1205
|
+
process.stdin.setEncoding('utf8');
|
|
1206
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
1207
|
+
process.stdin.on('end', () => {
|
|
1208
|
+
try {
|
|
1209
|
+
const data = JSON.parse(input);
|
|
1210
|
+
if (!EDIT_TOOLS.has(data.tool_name)) process.exit(0);
|
|
1201
1211
|
|
|
1212
|
+
const filePath = data.tool_input?.file_path || '';
|
|
1213
|
+
if (!filePath || shouldSkip(filePath)) process.exit(0);
|
|
1202
1214
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1215
|
+
const fileName = path.basename(filePath);
|
|
1216
|
+
console.log(
|
|
1217
|
+
'[SKILL TRIGGER] Editing: ' + fileName + '\\n' +
|
|
1218
|
+
'-> Call load_skills_for_files(files=[ "' + filePath + '" ]) on the ' +
|
|
1219
|
+
'agent-skills-standard MCP. It returns applicable SKILL.md rules, ' +
|
|
1220
|
+
'or nothing if no skills match this file type.'
|
|
1221
|
+
);
|
|
1222
|
+
process.exit(0);
|
|
1223
|
+
} catch {
|
|
1224
|
+
process.exit(0);
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1205
1227
|
`;
|
|
1206
1228
|
/**
|
|
1207
1229
|
* Kiro hook spec (Markdown + YAML frontmatter).
|
|
@@ -1221,9 +1243,6 @@ instructions: |
|
|
|
1221
1243
|
file being edited, or nothing if no skills match. Apply all returned rules strictly.
|
|
1222
1244
|
---
|
|
1223
1245
|
`;
|
|
1224
|
-
var CLAUDE_HOOK_SCRIPT_REL = ".claude/hooks/preedit-skill-loader.py";
|
|
1225
|
-
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
1226
|
-
var CLAUDE_HOOK_CMD = "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/preedit-skill-loader.py\"";
|
|
1227
1246
|
var CLAUDE_MCP_PERMISSION = "mcp__agent-skills-standard__*";
|
|
1228
1247
|
var KIRO_HOOK_REL = ".kiro/hooks/ags-skill-loader.md";
|
|
1229
1248
|
/**
|
|
@@ -1231,6 +1250,11 @@ var KIRO_HOOK_REL = ".kiro/hooks/ags-skill-loader.md";
|
|
|
1231
1250
|
*
|
|
1232
1251
|
* Supported hook systems:
|
|
1233
1252
|
* Claude Code — PreToolUse hook (blocking-capable, fires before every Edit/Write)
|
|
1253
|
+
* Cursor — PreToolUse hook
|
|
1254
|
+
* Windsurf — PreToolUse hook
|
|
1255
|
+
* Gemini — PreToolUse hook
|
|
1256
|
+
* Codex — PreToolUse hook
|
|
1257
|
+
* Copilot — PreToolUse hook
|
|
1234
1258
|
* Kiro — File-save spec hook (advisory, cannot block edits)
|
|
1235
1259
|
*
|
|
1236
1260
|
* All other agents in the supported-agents list have no programmatic hook system;
|
|
@@ -1247,29 +1271,28 @@ var HookService = class {
|
|
|
1247
1271
|
writes: [],
|
|
1248
1272
|
unsupported: []
|
|
1249
1273
|
};
|
|
1250
|
-
for (const agent of opts.agents)
|
|
1251
|
-
|
|
1252
|
-
await this.installClaudeHook(opts.rootDir, report);
|
|
1253
|
-
break;
|
|
1254
|
-
case Agent.Kiro:
|
|
1274
|
+
for (const agent of opts.agents) {
|
|
1275
|
+
if (agent === Agent.Kiro) {
|
|
1255
1276
|
await this.installKiroHook(opts.rootDir, report);
|
|
1256
|
-
|
|
1257
|
-
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
const def = getAgentDefinition(agent);
|
|
1280
|
+
if (def.hookScriptPath && def.hookConfigPath) await this.installStandardHookConfig({
|
|
1281
|
+
rootDir: opts.rootDir,
|
|
1282
|
+
agent,
|
|
1283
|
+
scriptRelPath: def.hookScriptPath,
|
|
1284
|
+
configRelPath: def.hookConfigPath,
|
|
1285
|
+
hookCmd: agent === Agent.Claude ? "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/preedit-skill-loader.js\"" : `node "${def.hookScriptPath}"`,
|
|
1286
|
+
report
|
|
1287
|
+
});
|
|
1288
|
+
else report.unsupported.push(agent);
|
|
1258
1289
|
}
|
|
1259
1290
|
return report;
|
|
1260
1291
|
}
|
|
1261
1292
|
async uninstall(opts) {
|
|
1262
1293
|
const removed = [];
|
|
1263
|
-
for (const agent of opts.agents)
|
|
1264
|
-
|
|
1265
|
-
const files = await this.uninstallClaudeHook(opts.rootDir);
|
|
1266
|
-
removed.push(...files.map((f) => ({
|
|
1267
|
-
agent,
|
|
1268
|
-
file: f
|
|
1269
|
-
})));
|
|
1270
|
-
break;
|
|
1271
|
-
}
|
|
1272
|
-
case Agent.Kiro: {
|
|
1294
|
+
for (const agent of opts.agents) {
|
|
1295
|
+
if (agent === Agent.Kiro) {
|
|
1273
1296
|
const abs = path.default.join(opts.rootDir, KIRO_HOOK_REL);
|
|
1274
1297
|
if (await fs_extra.default.pathExists(abs)) {
|
|
1275
1298
|
await fs_extra.default.remove(abs);
|
|
@@ -1278,57 +1301,57 @@ var HookService = class {
|
|
|
1278
1301
|
file: KIRO_HOOK_REL
|
|
1279
1302
|
});
|
|
1280
1303
|
}
|
|
1281
|
-
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
const def = getAgentDefinition(agent);
|
|
1307
|
+
if (def.hookConfigPath) {
|
|
1308
|
+
const files = await this.uninstallStandardHookConfig(opts.rootDir, def.hookConfigPath);
|
|
1309
|
+
removed.push(...files.map((f) => ({
|
|
1310
|
+
agent,
|
|
1311
|
+
file: f
|
|
1312
|
+
})));
|
|
1282
1313
|
}
|
|
1283
|
-
default: break;
|
|
1284
1314
|
}
|
|
1285
1315
|
return { removed };
|
|
1286
1316
|
}
|
|
1287
1317
|
async status(opts) {
|
|
1288
1318
|
const rows = [];
|
|
1289
|
-
for (const agent of opts.agents)
|
|
1290
|
-
|
|
1291
|
-
const scriptExists = await fs_extra.default.pathExists(path.default.join(opts.rootDir, CLAUDE_HOOK_SCRIPT_REL));
|
|
1292
|
-
const registeredInSettings = await this.claudeHookIsRegistered(path.default.join(opts.rootDir, CLAUDE_SETTINGS_REL));
|
|
1293
|
-
rows.push({
|
|
1294
|
-
agent,
|
|
1295
|
-
installed: scriptExists && registeredInSettings,
|
|
1296
|
-
files: [CLAUDE_HOOK_SCRIPT_REL, CLAUDE_SETTINGS_REL]
|
|
1297
|
-
});
|
|
1298
|
-
break;
|
|
1299
|
-
}
|
|
1300
|
-
case Agent.Kiro: {
|
|
1319
|
+
for (const agent of opts.agents) {
|
|
1320
|
+
if (agent === Agent.Kiro) {
|
|
1301
1321
|
const exists = await fs_extra.default.pathExists(path.default.join(opts.rootDir, KIRO_HOOK_REL));
|
|
1302
1322
|
rows.push({
|
|
1303
1323
|
agent,
|
|
1304
1324
|
installed: exists,
|
|
1305
1325
|
files: [KIRO_HOOK_REL]
|
|
1306
1326
|
});
|
|
1307
|
-
|
|
1327
|
+
continue;
|
|
1308
1328
|
}
|
|
1309
|
-
|
|
1329
|
+
const def = getAgentDefinition(agent);
|
|
1330
|
+
if (def.hookScriptPath && def.hookConfigPath) rows.push(await this.standardHookStatus(opts.rootDir, agent, def.hookScriptPath, def.hookConfigPath));
|
|
1310
1331
|
}
|
|
1311
1332
|
return rows;
|
|
1312
1333
|
}
|
|
1313
|
-
async
|
|
1314
|
-
const scriptAbs = path.default.join(rootDir,
|
|
1334
|
+
async installStandardHookConfig(opts) {
|
|
1335
|
+
const scriptAbs = path.default.join(opts.rootDir, opts.scriptRelPath);
|
|
1315
1336
|
const scriptExists = await fs_extra.default.pathExists(scriptAbs);
|
|
1316
1337
|
await fs_extra.default.ensureDir(path.default.dirname(scriptAbs));
|
|
1338
|
+
const legacyPyAbs = scriptAbs.replace(/\.js$/, ".py");
|
|
1339
|
+
if (await fs_extra.default.pathExists(legacyPyAbs)) await fs_extra.default.remove(legacyPyAbs).catch(() => {});
|
|
1317
1340
|
if (!scriptExists) {
|
|
1318
|
-
await fs_extra.default.writeFile(scriptAbs,
|
|
1319
|
-
report.writes.push({
|
|
1320
|
-
agent:
|
|
1321
|
-
file:
|
|
1341
|
+
await fs_extra.default.writeFile(scriptAbs, UNIVERSAL_SKILL_LOADER_JS);
|
|
1342
|
+
opts.report.writes.push({
|
|
1343
|
+
agent: opts.agent,
|
|
1344
|
+
file: opts.scriptRelPath,
|
|
1322
1345
|
action: "added"
|
|
1323
1346
|
});
|
|
1324
|
-
} else report.writes.push({
|
|
1325
|
-
agent:
|
|
1326
|
-
file:
|
|
1347
|
+
} else opts.report.writes.push({
|
|
1348
|
+
agent: opts.agent,
|
|
1349
|
+
file: opts.scriptRelPath,
|
|
1327
1350
|
action: "skipped-existing"
|
|
1328
1351
|
});
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
const existing = await this.readJson(
|
|
1352
|
+
const configAbs = path.default.join(opts.rootDir, opts.configRelPath);
|
|
1353
|
+
const configExists = await fs_extra.default.pathExists(configAbs);
|
|
1354
|
+
const existing = await this.readJson(configAbs);
|
|
1332
1355
|
let changed = false;
|
|
1333
1356
|
const permissions = this.ensureObject(existing, "permissions");
|
|
1334
1357
|
const allow = this.ensureArray(permissions, "allow");
|
|
@@ -1343,30 +1366,30 @@ var HookService = class {
|
|
|
1343
1366
|
matcher: "Edit|Write|MultiEdit|NotebookEdit",
|
|
1344
1367
|
hooks: [{
|
|
1345
1368
|
type: "command",
|
|
1346
|
-
command:
|
|
1369
|
+
command: opts.hookCmd,
|
|
1347
1370
|
timeout: 5
|
|
1348
1371
|
}]
|
|
1349
1372
|
});
|
|
1350
1373
|
changed = true;
|
|
1351
1374
|
}
|
|
1352
1375
|
if (changed) {
|
|
1353
|
-
await this.writeAtomic(
|
|
1354
|
-
report.writes.push({
|
|
1355
|
-
agent:
|
|
1356
|
-
file:
|
|
1357
|
-
action:
|
|
1376
|
+
await this.writeAtomic(configAbs, existing);
|
|
1377
|
+
opts.report.writes.push({
|
|
1378
|
+
agent: opts.agent,
|
|
1379
|
+
file: opts.configRelPath,
|
|
1380
|
+
action: configExists ? "updated" : "added"
|
|
1358
1381
|
});
|
|
1359
|
-
} else report.writes.push({
|
|
1360
|
-
agent:
|
|
1361
|
-
file:
|
|
1382
|
+
} else opts.report.writes.push({
|
|
1383
|
+
agent: opts.agent,
|
|
1384
|
+
file: opts.configRelPath,
|
|
1362
1385
|
action: "skipped-existing"
|
|
1363
1386
|
});
|
|
1364
1387
|
}
|
|
1365
|
-
async
|
|
1388
|
+
async uninstallStandardHookConfig(rootDir, configRelPath) {
|
|
1366
1389
|
const removed = [];
|
|
1367
|
-
const
|
|
1368
|
-
if (!await fs_extra.default.pathExists(
|
|
1369
|
-
const data = await this.readJson(
|
|
1390
|
+
const configAbs = path.default.join(rootDir, configRelPath);
|
|
1391
|
+
if (!await fs_extra.default.pathExists(configAbs)) return removed;
|
|
1392
|
+
const data = await this.readJson(configAbs);
|
|
1370
1393
|
let changed = false;
|
|
1371
1394
|
const permissions = this.ensureObject(data, "permissions");
|
|
1372
1395
|
const allow = this.ensureArray(permissions, "allow");
|
|
@@ -1382,11 +1405,20 @@ var HookService = class {
|
|
|
1382
1405
|
changed = true;
|
|
1383
1406
|
}
|
|
1384
1407
|
if (changed) {
|
|
1385
|
-
await this.writeAtomic(
|
|
1386
|
-
removed.push(
|
|
1408
|
+
await this.writeAtomic(configAbs, data);
|
|
1409
|
+
removed.push(configRelPath);
|
|
1387
1410
|
}
|
|
1388
1411
|
return removed;
|
|
1389
1412
|
}
|
|
1413
|
+
async standardHookStatus(rootDir, agent, scriptRelPath, configRelPath) {
|
|
1414
|
+
const scriptExists = await fs_extra.default.pathExists(path.default.join(rootDir, scriptRelPath));
|
|
1415
|
+
const registeredInSettings = await this.claudeHookIsRegistered(path.default.join(rootDir, configRelPath));
|
|
1416
|
+
return {
|
|
1417
|
+
agent,
|
|
1418
|
+
installed: scriptExists && registeredInSettings,
|
|
1419
|
+
files: [scriptRelPath, configRelPath]
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1390
1422
|
async claudeHookIsRegistered(settingsAbs) {
|
|
1391
1423
|
if (!await fs_extra.default.pathExists(settingsAbs)) return false;
|
|
1392
1424
|
const hooks = (await this.readJson(settingsAbs))["hooks"];
|
|
@@ -1794,9 +1826,8 @@ var InitService = class {
|
|
|
1794
1826
|
#
|
|
1795
1827
|
# Run 'ags list-skills' to view all available skills.
|
|
1796
1828
|
#
|
|
1797
|
-
#
|
|
1798
|
-
# skills
|
|
1799
|
-
# quality-engineering: { ref: quality-engineering-v1.0.0 }
|
|
1829
|
+
# SDLC support skills such as quality-engineering are included by default.
|
|
1830
|
+
# Specialist definitions are synced separately as native sub-agents, not skills.
|
|
1800
1831
|
#
|
|
1801
1832
|
`;
|
|
1802
1833
|
const configPath = path.default.join(cwd, ".skillsrc");
|
|
@@ -2292,27 +2323,27 @@ var getTargets = (home = os.default.homedir()) => {
|
|
|
2292
2323
|
},
|
|
2293
2324
|
[Agent.Copilot]: {
|
|
2294
2325
|
agent: Agent.Copilot,
|
|
2295
|
-
projectFile: ".
|
|
2326
|
+
projectFile: ".github/mcp.json",
|
|
2296
2327
|
userFile: (() => {
|
|
2297
2328
|
if (process.platform === "darwin") return path.default.join(HOME, "Library", "Application Support", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2298
2329
|
if (process.platform === "win32") return path.default.join(HOME, "AppData", "Roaming", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2299
2330
|
if (process.platform === "linux") return path.default.join(HOME, ".config", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2300
2331
|
return null;
|
|
2301
2332
|
})(),
|
|
2302
|
-
key: "
|
|
2333
|
+
key: "mcpServers",
|
|
2303
2334
|
shape: "map"
|
|
2304
2335
|
},
|
|
2305
2336
|
[Agent.OpenCode]: {
|
|
2306
2337
|
agent: Agent.OpenCode,
|
|
2307
|
-
projectFile: "opencode.json",
|
|
2308
|
-
userFile: path.default.join(HOME, ".
|
|
2309
|
-
key: "
|
|
2338
|
+
projectFile: ".opencode/mcp_config.json",
|
|
2339
|
+
userFile: path.default.join(HOME, ".opencode", "mcp_config.json"),
|
|
2340
|
+
key: "mcpServers",
|
|
2310
2341
|
shape: "map"
|
|
2311
2342
|
},
|
|
2312
|
-
[Agent.
|
|
2313
|
-
agent: Agent.
|
|
2314
|
-
projectFile: ".codex/
|
|
2315
|
-
userFile: path.default.join(HOME, ".codex", "
|
|
2343
|
+
[Agent.Codex]: {
|
|
2344
|
+
agent: Agent.Codex,
|
|
2345
|
+
projectFile: ".codex/mcp_config.json",
|
|
2346
|
+
userFile: path.default.join(HOME, ".codex", "mcp_config.json"),
|
|
2316
2347
|
key: "mcpServers",
|
|
2317
2348
|
shape: "map"
|
|
2318
2349
|
}
|
|
@@ -3657,10 +3688,22 @@ Follow the exact steps in the workflow file.
|
|
|
3657
3688
|
* Invoked through each platform's skill mechanism.
|
|
3658
3689
|
*/
|
|
3659
3690
|
static toSkillMarkdown(name, description, body) {
|
|
3660
|
-
|
|
3691
|
+
const normalizedDescription = description || `Workflow skill for ${name.replace(/-/g, " ")}.`;
|
|
3692
|
+
const escapedDescription = normalizedDescription.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
3693
|
+
const title = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3694
|
+
return `---
|
|
3695
|
+
name: ${name}
|
|
3696
|
+
description: "${escapedDescription}"
|
|
3697
|
+
metadata:
|
|
3698
|
+
triggers:
|
|
3699
|
+
keywords:
|
|
3700
|
+
- ${name.replace(/-/g, " ")}
|
|
3701
|
+
- workflow
|
|
3702
|
+
---
|
|
3703
|
+
# ${title} Skill
|
|
3661
3704
|
|
|
3662
3705
|
> [!IMPORTANT]
|
|
3663
|
-
> ${
|
|
3706
|
+
> ${normalizedDescription}
|
|
3664
3707
|
|
|
3665
3708
|
## Instructions
|
|
3666
3709
|
|
|
@@ -3856,7 +3899,7 @@ var SpecialistTransformer = class {
|
|
|
3856
3899
|
name: `${baseName}.md`,
|
|
3857
3900
|
content: `---\nname: ${baseName}\ndescription: "${description}"\n---\n\n${body}`
|
|
3858
3901
|
};
|
|
3859
|
-
case Agent.
|
|
3902
|
+
case Agent.Codex: return {
|
|
3860
3903
|
name: `${baseName}.toml`,
|
|
3861
3904
|
content: `name = "${baseName}"\ndescription = "${description}"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\n${body}\n"""`
|
|
3862
3905
|
};
|
|
@@ -3874,6 +3917,56 @@ var SpecialistTransformer = class {
|
|
|
3874
3917
|
* to native agent configuration directories.
|
|
3875
3918
|
*/
|
|
3876
3919
|
var SpecialistSyncService = class {
|
|
3920
|
+
constructor(githubService = new GithubService(process.env.GITHUB_TOKEN)) {
|
|
3921
|
+
this.githubService = githubService;
|
|
3922
|
+
}
|
|
3923
|
+
async assembleSpecialists(config) {
|
|
3924
|
+
const githubMatch = GithubService.parseGitHubUrl(config.registry);
|
|
3925
|
+
if (!githubMatch) return [];
|
|
3926
|
+
const { owner, repo } = githubMatch;
|
|
3927
|
+
const ref = (await this.githubService.getRepoInfo(owner, repo))?.default_branch || "main";
|
|
3928
|
+
const treeData = await this.githubService.getRepoTree(owner, repo, ref);
|
|
3929
|
+
if (!treeData) return [];
|
|
3930
|
+
const skillFiles = treeData.tree.filter((item) => item.type === "blob" && item.path.startsWith("skills/specialists/") && item.path.endsWith("/SKILL.md"));
|
|
3931
|
+
const specialists = [];
|
|
3932
|
+
for (const file of skillFiles) {
|
|
3933
|
+
const specialistName = file.path.split("/")[2];
|
|
3934
|
+
if (!specialistName) continue;
|
|
3935
|
+
const content = await this.githubService.getRawFile(owner, repo, ref, file.path);
|
|
3936
|
+
if (!content) continue;
|
|
3937
|
+
specialists.push({
|
|
3938
|
+
category: "specialists",
|
|
3939
|
+
skill: specialistName,
|
|
3940
|
+
files: [{
|
|
3941
|
+
name: "SKILL.md",
|
|
3942
|
+
content
|
|
3943
|
+
}]
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
return specialists;
|
|
3947
|
+
}
|
|
3948
|
+
async syncCollectedSpecialists(rootDir, agents, specialists) {
|
|
3949
|
+
if (specialists.length === 0) return;
|
|
3950
|
+
for (const agentId of agents) {
|
|
3951
|
+
const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
|
|
3952
|
+
if (!agentDef || !agentDef.agentPath) continue;
|
|
3953
|
+
const targetDir = path.default.join(rootDir, agentDef.agentPath);
|
|
3954
|
+
await fs_extra.default.ensureDir(targetDir);
|
|
3955
|
+
let syncedCount = 0;
|
|
3956
|
+
for (const specialist of specialists) {
|
|
3957
|
+
const skillFile = specialist.files.find((file) => file.name === "SKILL.md");
|
|
3958
|
+
if (!skillFile) continue;
|
|
3959
|
+
const transformed = SpecialistTransformer.transform({
|
|
3960
|
+
name: specialist.skill,
|
|
3961
|
+
content: skillFile.content
|
|
3962
|
+
}, agentId);
|
|
3963
|
+
if (!transformed) continue;
|
|
3964
|
+
await fs_extra.default.outputFile(path.default.join(targetDir, transformed.name), transformed.content);
|
|
3965
|
+
syncedCount++;
|
|
3966
|
+
}
|
|
3967
|
+
if (syncedCount > 0) console.log(picocolors.default.green(` ✅ ${syncedCount} specialists synced to ${agentDef.agentPath}/ (${agentDef.name})`));
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3877
3970
|
/**
|
|
3878
3971
|
* Syncs all specialists from a source directory to active agents.
|
|
3879
3972
|
* @param rootDir Project root directory
|
|
@@ -3886,28 +3979,21 @@ var SpecialistSyncService = class {
|
|
|
3886
3979
|
const specialistFolders = (await fs_extra.default.readdir(specialistsDir)).filter((f) => {
|
|
3887
3980
|
return fs_extra.default.statSync(path.default.join(specialistsDir, f)).isDirectory();
|
|
3888
3981
|
});
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
await fs_extra.default.
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
const transformed = SpecialistTransformer.transform({
|
|
3900
|
-
name: folder,
|
|
3982
|
+
const collected = [];
|
|
3983
|
+
for (const folder of specialistFolders) {
|
|
3984
|
+
const skillPath = path.default.join(specialistsDir, folder, "SKILL.md");
|
|
3985
|
+
if (!await fs_extra.default.pathExists(skillPath)) continue;
|
|
3986
|
+
const content = await fs_extra.default.readFile(skillPath, "utf8");
|
|
3987
|
+
collected.push({
|
|
3988
|
+
category: "specialists",
|
|
3989
|
+
skill: folder,
|
|
3990
|
+
files: [{
|
|
3991
|
+
name: "SKILL.md",
|
|
3901
3992
|
content
|
|
3902
|
-
}
|
|
3903
|
-
|
|
3904
|
-
const targetFile = path.default.join(targetDir, transformed.name);
|
|
3905
|
-
await fs_extra.default.outputFile(targetFile, transformed.content);
|
|
3906
|
-
syncedCount++;
|
|
3907
|
-
}
|
|
3908
|
-
}
|
|
3909
|
-
if (syncedCount > 0) console.log(picocolors.default.green(` ✅ ${syncedCount} specialists synced to ${agentDef.agentPath}/ (${agentDef.name})`));
|
|
3993
|
+
}]
|
|
3994
|
+
});
|
|
3910
3995
|
}
|
|
3996
|
+
return this.syncCollectedSpecialists(rootDir, agents, collected);
|
|
3911
3997
|
}
|
|
3912
3998
|
};
|
|
3913
3999
|
//#endregion
|
|
@@ -4063,7 +4149,8 @@ var SyncService = class {
|
|
|
4063
4149
|
}
|
|
4064
4150
|
async assembleSkills(categories, config) {
|
|
4065
4151
|
await this.warnIfSyncingFromSameRepo(config);
|
|
4066
|
-
|
|
4152
|
+
const skillCategories = categories.filter((category) => category !== "specialists");
|
|
4153
|
+
return this.skillSyncService.assembleSkills(skillCategories, config);
|
|
4067
4154
|
}
|
|
4068
4155
|
async warnIfSyncingFromSameRepo(config) {
|
|
4069
4156
|
const remoteUrl = this.gitService.getRemoteUrl(process.cwd());
|
|
@@ -4090,10 +4177,10 @@ var SyncService = class {
|
|
|
4090
4177
|
async syncSpecialists(config) {
|
|
4091
4178
|
const agents = await this.resolveTargetAgents(config);
|
|
4092
4179
|
if (agents.length === 0) return;
|
|
4093
|
-
const
|
|
4094
|
-
|
|
4095
|
-
const
|
|
4096
|
-
return this.specialistSyncService.
|
|
4180
|
+
const localRegistrySource = path.default.join(process.cwd(), "skills/specialists");
|
|
4181
|
+
if (await fs_extra.default.pathExists(localRegistrySource)) return this.specialistSyncService.syncSpecialists(process.cwd(), agents, localRegistrySource);
|
|
4182
|
+
const specialists = await this.specialistSyncService.assembleSpecialists(config);
|
|
4183
|
+
return this.specialistSyncService.syncCollectedSpecialists(process.cwd(), agents, specialists);
|
|
4097
4184
|
}
|
|
4098
4185
|
async applyIndices(config, targetAgents) {
|
|
4099
4186
|
const agents = targetAgents || await this.resolveTargetAgents(config);
|
|
@@ -4356,6 +4443,14 @@ var SyncCommand = class {
|
|
|
4356
4443
|
async runHookPhase(config) {
|
|
4357
4444
|
const agents = config.agents ?? [];
|
|
4358
4445
|
if (agents.length === 0) return;
|
|
4446
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
4447
|
+
if (!mcp.enabled || mcp.scope === "disabled") {
|
|
4448
|
+
await this.hookService.uninstall({
|
|
4449
|
+
rootDir: process.cwd(),
|
|
4450
|
+
agents
|
|
4451
|
+
});
|
|
4452
|
+
return;
|
|
4453
|
+
}
|
|
4359
4454
|
const writes = (await this.hookService.install({
|
|
4360
4455
|
rootDir: process.cwd(),
|
|
4361
4456
|
agents
|
|
@@ -4370,7 +4465,7 @@ var SyncCommand = class {
|
|
|
4370
4465
|
};
|
|
4371
4466
|
var package_default = {
|
|
4372
4467
|
name: "agent-skills-standard",
|
|
4373
|
-
version: "2.
|
|
4468
|
+
version: "2.4.0",
|
|
4374
4469
|
description: "A CLI to manage and sync AI agent skills standard for Cursor, Claude, Copilot, Windsurf, and more.",
|
|
4375
4470
|
main: "dist/index.js",
|
|
4376
4471
|
bin: {
|
|
@@ -4856,7 +4951,7 @@ var ValidateCommand = class {
|
|
|
4856
4951
|
//#region src/index.ts
|
|
4857
4952
|
dotenv.default.config();
|
|
4858
4953
|
var program = new commander.Command();
|
|
4859
|
-
program.name("agent-skills-standard").description("A CLI to manage and sync AI agent skills for Cursor, Claude, Copilot, Windsurf, and more.").version("2.
|
|
4954
|
+
program.name("agent-skills-standard").description("A CLI to manage and sync AI agent skills for Cursor, Claude, Copilot, Windsurf, and more.").version("2.4.0");
|
|
4860
4955
|
program.command("init").description("Initialize a .skillsrc configuration file interactively").action(async () => {
|
|
4861
4956
|
await new InitCommand().run();
|
|
4862
4957
|
});
|