agent-skills-standard 2.3.0 → 2.4.1
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 +288 -186
- 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
|
};
|
|
@@ -949,7 +973,10 @@ var McpConfigSchema = zod.z.object({
|
|
|
949
973
|
});
|
|
950
974
|
var SkillConfigSchema = zod.z.object({
|
|
951
975
|
registry: zod.z.string().url(),
|
|
952
|
-
agents: zod.z.
|
|
976
|
+
agents: zod.z.preprocess((val) => {
|
|
977
|
+
if (Array.isArray(val)) return val.map((a) => typeof a === "string" && a.toLowerCase() === "openai" ? "codex" : a);
|
|
978
|
+
return val;
|
|
979
|
+
}, zod.z.array(zod.z.nativeEnum(Agent)).optional()),
|
|
953
980
|
skills: zod.z.record(zod.z.string(), zod.z.object({
|
|
954
981
|
ref: zod.z.string().optional(),
|
|
955
982
|
include: zod.z.array(zod.z.string()).optional(),
|
|
@@ -959,11 +986,17 @@ var SkillConfigSchema = zod.z.object({
|
|
|
959
986
|
workflows: zod.z.union([zod.z.boolean(), zod.z.array(zod.z.string())]).optional(),
|
|
960
987
|
mcp: McpConfigSchema.optional()
|
|
961
988
|
});
|
|
989
|
+
var DEFAULT_SDLC_SUPPORT_CATEGORIES = ["quality-engineering"];
|
|
962
990
|
/**
|
|
963
991
|
* Service for managing the `.skillsrc` configuration file.
|
|
964
992
|
* Handles loading, saving, and initial construction of the configuration based on project metadata.
|
|
965
993
|
*/
|
|
966
994
|
var ConfigService = class {
|
|
995
|
+
getCategoryRef(category, metadata) {
|
|
996
|
+
const categoryMetadata = metadata.categories?.[category];
|
|
997
|
+
if (!categoryMetadata?.version) return "main";
|
|
998
|
+
return `${categoryMetadata.tag_prefix || ""}${categoryMetadata.version}`;
|
|
999
|
+
}
|
|
967
1000
|
/**
|
|
968
1001
|
* Loads and validates the skill configuration from the workspace.
|
|
969
1002
|
* @param cwd Current working directory
|
|
@@ -976,6 +1009,20 @@ var ConfigService = class {
|
|
|
976
1009
|
try {
|
|
977
1010
|
const content = await fs_extra.default.readFile(configPath, "utf8");
|
|
978
1011
|
const rawConfig = js_yaml.default.load(content);
|
|
1012
|
+
if (rawConfig && Array.isArray(rawConfig.agents)) {
|
|
1013
|
+
let needsMigration = false;
|
|
1014
|
+
const updatedAgents = rawConfig.agents.map((agent) => {
|
|
1015
|
+
if (typeof agent === "string" && agent.toLowerCase() === "openai") {
|
|
1016
|
+
needsMigration = true;
|
|
1017
|
+
return Agent.Codex;
|
|
1018
|
+
}
|
|
1019
|
+
return agent;
|
|
1020
|
+
});
|
|
1021
|
+
if (needsMigration) {
|
|
1022
|
+
rawConfig.agents = updatedAgents;
|
|
1023
|
+
await fs_extra.default.outputFile(configPath, js_yaml.default.dump(rawConfig));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
979
1026
|
const parsed = SkillConfigSchema.safeParse(rawConfig);
|
|
980
1027
|
if (!parsed.success) throw new Error(`Invalid .skillsrc format: ${parsed.error.message}`);
|
|
981
1028
|
return parsed.data;
|
|
@@ -1015,6 +1062,7 @@ var ConfigService = class {
|
|
|
1015
1062
|
...commonExcludes.length > 0 && { exclude: commonExcludes }
|
|
1016
1063
|
};
|
|
1017
1064
|
}
|
|
1065
|
+
for (const category of DEFAULT_SDLC_SUPPORT_CATEGORIES) if (metadata.categories?.[category] && !skills[category]) skills[category] = { ref: this.getCategoryRef(category, metadata) };
|
|
1018
1066
|
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
1067
|
return {
|
|
1020
1068
|
registry,
|
|
@@ -1124,84 +1172,75 @@ var ConfigService = class {
|
|
|
1124
1172
|
//#endregion
|
|
1125
1173
|
//#region src/services/HookService.ts
|
|
1126
1174
|
/**
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
* Always exits 0
|
|
1175
|
+
* Universal PreToolUse hook script (Node.js / JavaScript).
|
|
1176
|
+
* Reminds AI agents to call load_skills_for_files() before any code edit.
|
|
1177
|
+
* Guaranteed to execute on any machine running agent-skills-standard.
|
|
1178
|
+
* Always exits 0 to never block work.
|
|
1131
1179
|
*/
|
|
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)
|
|
1180
|
+
var UNIVERSAL_SKILL_LOADER_JS = `#!/usr/bin/env node
|
|
1181
|
+
/**
|
|
1182
|
+
* PreToolUse hook: remind AI agent to call load_skills_for_files() before any code edit.
|
|
1183
|
+
* Installed by agent-skills-standard (ags sync). Remove via: ags hooks uninstall
|
|
1184
|
+
* Guaranteed to execute on any system with Node.js. Always exits 0 to never block work.
|
|
1185
|
+
*/
|
|
1186
|
+
const fs = require('fs');
|
|
1187
|
+
const path = require('path');
|
|
1188
1188
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1189
|
+
const REPO_ROOT = process.env.CLAUDE_PROJECT_DIR || path.resolve(__dirname, '../../');
|
|
1190
|
+
const EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
1191
|
+
const SKIP_DIRS = [
|
|
1192
|
+
path.resolve(REPO_ROOT, '.claude'),
|
|
1193
|
+
path.resolve(REPO_ROOT, '.gemini'),
|
|
1194
|
+
path.resolve(REPO_ROOT, '.codex'),
|
|
1195
|
+
path.resolve(REPO_ROOT, '.github'),
|
|
1196
|
+
path.resolve(REPO_ROOT, '.cursor'),
|
|
1197
|
+
path.resolve(REPO_ROOT, '.roo'),
|
|
1198
|
+
path.resolve(REPO_ROOT, '.trae'),
|
|
1199
|
+
path.resolve(REPO_ROOT, '.opencode'),
|
|
1200
|
+
path.resolve(REPO_ROOT, '.kiro'),
|
|
1201
|
+
path.resolve(REPO_ROOT, '.windsurf'),
|
|
1202
|
+
path.resolve(REPO_ROOT, '.agents'),
|
|
1203
|
+
path.resolve(REPO_ROOT, '.vscode'),
|
|
1204
|
+
];
|
|
1205
|
+
const SKIP_FILES = [
|
|
1206
|
+
path.resolve(REPO_ROOT, 'AGENTS.md'),
|
|
1207
|
+
path.resolve(REPO_ROOT, 'CLAUDE.md'),
|
|
1208
|
+
];
|
|
1192
1209
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1210
|
+
function shouldSkip(filePath) {
|
|
1211
|
+
try {
|
|
1212
|
+
const real = path.resolve(filePath);
|
|
1213
|
+
if (SKIP_DIRS.some(d => real.startsWith(d))) return true;
|
|
1214
|
+
if (SKIP_FILES.includes(real)) return true;
|
|
1215
|
+
return false;
|
|
1216
|
+
} catch {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1199
1220
|
|
|
1200
|
-
|
|
1221
|
+
let input = '';
|
|
1222
|
+
process.stdin.setEncoding('utf8');
|
|
1223
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
1224
|
+
process.stdin.on('end', () => {
|
|
1225
|
+
try {
|
|
1226
|
+
const data = JSON.parse(input);
|
|
1227
|
+
if (!EDIT_TOOLS.has(data.tool_name)) process.exit(0);
|
|
1201
1228
|
|
|
1229
|
+
const filePath = data.tool_input?.file_path || '';
|
|
1230
|
+
if (!filePath || shouldSkip(filePath)) process.exit(0);
|
|
1202
1231
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1232
|
+
const fileName = path.basename(filePath);
|
|
1233
|
+
console.log(
|
|
1234
|
+
'[SKILL TRIGGER] Editing: ' + fileName + '\\n' +
|
|
1235
|
+
'-> Call load_skills_for_files(files=[ "' + filePath + '" ]) on the ' +
|
|
1236
|
+
'agent-skills-standard MCP. It returns applicable SKILL.md rules, ' +
|
|
1237
|
+
'or nothing if no skills match this file type.'
|
|
1238
|
+
);
|
|
1239
|
+
process.exit(0);
|
|
1240
|
+
} catch {
|
|
1241
|
+
process.exit(0);
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1205
1244
|
`;
|
|
1206
1245
|
/**
|
|
1207
1246
|
* Kiro hook spec (Markdown + YAML frontmatter).
|
|
@@ -1221,9 +1260,6 @@ instructions: |
|
|
|
1221
1260
|
file being edited, or nothing if no skills match. Apply all returned rules strictly.
|
|
1222
1261
|
---
|
|
1223
1262
|
`;
|
|
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
1263
|
var CLAUDE_MCP_PERMISSION = "mcp__agent-skills-standard__*";
|
|
1228
1264
|
var KIRO_HOOK_REL = ".kiro/hooks/ags-skill-loader.md";
|
|
1229
1265
|
/**
|
|
@@ -1231,6 +1267,11 @@ var KIRO_HOOK_REL = ".kiro/hooks/ags-skill-loader.md";
|
|
|
1231
1267
|
*
|
|
1232
1268
|
* Supported hook systems:
|
|
1233
1269
|
* Claude Code — PreToolUse hook (blocking-capable, fires before every Edit/Write)
|
|
1270
|
+
* Cursor — PreToolUse hook
|
|
1271
|
+
* Windsurf — PreToolUse hook
|
|
1272
|
+
* Gemini — PreToolUse hook
|
|
1273
|
+
* Codex — PreToolUse hook
|
|
1274
|
+
* Copilot — PreToolUse hook
|
|
1234
1275
|
* Kiro — File-save spec hook (advisory, cannot block edits)
|
|
1235
1276
|
*
|
|
1236
1277
|
* All other agents in the supported-agents list have no programmatic hook system;
|
|
@@ -1247,29 +1288,28 @@ var HookService = class {
|
|
|
1247
1288
|
writes: [],
|
|
1248
1289
|
unsupported: []
|
|
1249
1290
|
};
|
|
1250
|
-
for (const agent of opts.agents)
|
|
1251
|
-
|
|
1252
|
-
await this.installClaudeHook(opts.rootDir, report);
|
|
1253
|
-
break;
|
|
1254
|
-
case Agent.Kiro:
|
|
1291
|
+
for (const agent of opts.agents) {
|
|
1292
|
+
if (agent === Agent.Kiro) {
|
|
1255
1293
|
await this.installKiroHook(opts.rootDir, report);
|
|
1256
|
-
|
|
1257
|
-
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
const def = getAgentDefinition(agent);
|
|
1297
|
+
if (def.hookScriptPath && def.hookConfigPath) await this.installStandardHookConfig({
|
|
1298
|
+
rootDir: opts.rootDir,
|
|
1299
|
+
agent,
|
|
1300
|
+
scriptRelPath: def.hookScriptPath,
|
|
1301
|
+
configRelPath: def.hookConfigPath,
|
|
1302
|
+
hookCmd: agent === Agent.Claude ? "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/preedit-skill-loader.js\"" : `node "${def.hookScriptPath}"`,
|
|
1303
|
+
report
|
|
1304
|
+
});
|
|
1305
|
+
else report.unsupported.push(agent);
|
|
1258
1306
|
}
|
|
1259
1307
|
return report;
|
|
1260
1308
|
}
|
|
1261
1309
|
async uninstall(opts) {
|
|
1262
1310
|
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: {
|
|
1311
|
+
for (const agent of opts.agents) {
|
|
1312
|
+
if (agent === Agent.Kiro) {
|
|
1273
1313
|
const abs = path.default.join(opts.rootDir, KIRO_HOOK_REL);
|
|
1274
1314
|
if (await fs_extra.default.pathExists(abs)) {
|
|
1275
1315
|
await fs_extra.default.remove(abs);
|
|
@@ -1278,57 +1318,57 @@ var HookService = class {
|
|
|
1278
1318
|
file: KIRO_HOOK_REL
|
|
1279
1319
|
});
|
|
1280
1320
|
}
|
|
1281
|
-
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const def = getAgentDefinition(agent);
|
|
1324
|
+
if (def.hookConfigPath) {
|
|
1325
|
+
const files = await this.uninstallStandardHookConfig(opts.rootDir, def.hookConfigPath);
|
|
1326
|
+
removed.push(...files.map((f) => ({
|
|
1327
|
+
agent,
|
|
1328
|
+
file: f
|
|
1329
|
+
})));
|
|
1282
1330
|
}
|
|
1283
|
-
default: break;
|
|
1284
1331
|
}
|
|
1285
1332
|
return { removed };
|
|
1286
1333
|
}
|
|
1287
1334
|
async status(opts) {
|
|
1288
1335
|
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: {
|
|
1336
|
+
for (const agent of opts.agents) {
|
|
1337
|
+
if (agent === Agent.Kiro) {
|
|
1301
1338
|
const exists = await fs_extra.default.pathExists(path.default.join(opts.rootDir, KIRO_HOOK_REL));
|
|
1302
1339
|
rows.push({
|
|
1303
1340
|
agent,
|
|
1304
1341
|
installed: exists,
|
|
1305
1342
|
files: [KIRO_HOOK_REL]
|
|
1306
1343
|
});
|
|
1307
|
-
|
|
1344
|
+
continue;
|
|
1308
1345
|
}
|
|
1309
|
-
|
|
1346
|
+
const def = getAgentDefinition(agent);
|
|
1347
|
+
if (def.hookScriptPath && def.hookConfigPath) rows.push(await this.standardHookStatus(opts.rootDir, agent, def.hookScriptPath, def.hookConfigPath));
|
|
1310
1348
|
}
|
|
1311
1349
|
return rows;
|
|
1312
1350
|
}
|
|
1313
|
-
async
|
|
1314
|
-
const scriptAbs = path.default.join(rootDir,
|
|
1351
|
+
async installStandardHookConfig(opts) {
|
|
1352
|
+
const scriptAbs = path.default.join(opts.rootDir, opts.scriptRelPath);
|
|
1315
1353
|
const scriptExists = await fs_extra.default.pathExists(scriptAbs);
|
|
1316
1354
|
await fs_extra.default.ensureDir(path.default.dirname(scriptAbs));
|
|
1355
|
+
const legacyPyAbs = scriptAbs.replace(/\.js$/, ".py");
|
|
1356
|
+
if (await fs_extra.default.pathExists(legacyPyAbs)) await fs_extra.default.remove(legacyPyAbs).catch(() => {});
|
|
1317
1357
|
if (!scriptExists) {
|
|
1318
|
-
await fs_extra.default.writeFile(scriptAbs,
|
|
1319
|
-
report.writes.push({
|
|
1320
|
-
agent:
|
|
1321
|
-
file:
|
|
1358
|
+
await fs_extra.default.writeFile(scriptAbs, UNIVERSAL_SKILL_LOADER_JS);
|
|
1359
|
+
opts.report.writes.push({
|
|
1360
|
+
agent: opts.agent,
|
|
1361
|
+
file: opts.scriptRelPath,
|
|
1322
1362
|
action: "added"
|
|
1323
1363
|
});
|
|
1324
|
-
} else report.writes.push({
|
|
1325
|
-
agent:
|
|
1326
|
-
file:
|
|
1364
|
+
} else opts.report.writes.push({
|
|
1365
|
+
agent: opts.agent,
|
|
1366
|
+
file: opts.scriptRelPath,
|
|
1327
1367
|
action: "skipped-existing"
|
|
1328
1368
|
});
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
const existing = await this.readJson(
|
|
1369
|
+
const configAbs = path.default.join(opts.rootDir, opts.configRelPath);
|
|
1370
|
+
const configExists = await fs_extra.default.pathExists(configAbs);
|
|
1371
|
+
const existing = await this.readJson(configAbs);
|
|
1332
1372
|
let changed = false;
|
|
1333
1373
|
const permissions = this.ensureObject(existing, "permissions");
|
|
1334
1374
|
const allow = this.ensureArray(permissions, "allow");
|
|
@@ -1343,30 +1383,30 @@ var HookService = class {
|
|
|
1343
1383
|
matcher: "Edit|Write|MultiEdit|NotebookEdit",
|
|
1344
1384
|
hooks: [{
|
|
1345
1385
|
type: "command",
|
|
1346
|
-
command:
|
|
1386
|
+
command: opts.hookCmd,
|
|
1347
1387
|
timeout: 5
|
|
1348
1388
|
}]
|
|
1349
1389
|
});
|
|
1350
1390
|
changed = true;
|
|
1351
1391
|
}
|
|
1352
1392
|
if (changed) {
|
|
1353
|
-
await this.writeAtomic(
|
|
1354
|
-
report.writes.push({
|
|
1355
|
-
agent:
|
|
1356
|
-
file:
|
|
1357
|
-
action:
|
|
1393
|
+
await this.writeAtomic(configAbs, existing);
|
|
1394
|
+
opts.report.writes.push({
|
|
1395
|
+
agent: opts.agent,
|
|
1396
|
+
file: opts.configRelPath,
|
|
1397
|
+
action: configExists ? "updated" : "added"
|
|
1358
1398
|
});
|
|
1359
|
-
} else report.writes.push({
|
|
1360
|
-
agent:
|
|
1361
|
-
file:
|
|
1399
|
+
} else opts.report.writes.push({
|
|
1400
|
+
agent: opts.agent,
|
|
1401
|
+
file: opts.configRelPath,
|
|
1362
1402
|
action: "skipped-existing"
|
|
1363
1403
|
});
|
|
1364
1404
|
}
|
|
1365
|
-
async
|
|
1405
|
+
async uninstallStandardHookConfig(rootDir, configRelPath) {
|
|
1366
1406
|
const removed = [];
|
|
1367
|
-
const
|
|
1368
|
-
if (!await fs_extra.default.pathExists(
|
|
1369
|
-
const data = await this.readJson(
|
|
1407
|
+
const configAbs = path.default.join(rootDir, configRelPath);
|
|
1408
|
+
if (!await fs_extra.default.pathExists(configAbs)) return removed;
|
|
1409
|
+
const data = await this.readJson(configAbs);
|
|
1370
1410
|
let changed = false;
|
|
1371
1411
|
const permissions = this.ensureObject(data, "permissions");
|
|
1372
1412
|
const allow = this.ensureArray(permissions, "allow");
|
|
@@ -1382,11 +1422,20 @@ var HookService = class {
|
|
|
1382
1422
|
changed = true;
|
|
1383
1423
|
}
|
|
1384
1424
|
if (changed) {
|
|
1385
|
-
await this.writeAtomic(
|
|
1386
|
-
removed.push(
|
|
1425
|
+
await this.writeAtomic(configAbs, data);
|
|
1426
|
+
removed.push(configRelPath);
|
|
1387
1427
|
}
|
|
1388
1428
|
return removed;
|
|
1389
1429
|
}
|
|
1430
|
+
async standardHookStatus(rootDir, agent, scriptRelPath, configRelPath) {
|
|
1431
|
+
const scriptExists = await fs_extra.default.pathExists(path.default.join(rootDir, scriptRelPath));
|
|
1432
|
+
const registeredInSettings = await this.claudeHookIsRegistered(path.default.join(rootDir, configRelPath));
|
|
1433
|
+
return {
|
|
1434
|
+
agent,
|
|
1435
|
+
installed: scriptExists && registeredInSettings,
|
|
1436
|
+
files: [scriptRelPath, configRelPath]
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1390
1439
|
async claudeHookIsRegistered(settingsAbs) {
|
|
1391
1440
|
if (!await fs_extra.default.pathExists(settingsAbs)) return false;
|
|
1392
1441
|
const hooks = (await this.readJson(settingsAbs))["hooks"];
|
|
@@ -1794,9 +1843,8 @@ var InitService = class {
|
|
|
1794
1843
|
#
|
|
1795
1844
|
# Run 'ags list-skills' to view all available skills.
|
|
1796
1845
|
#
|
|
1797
|
-
#
|
|
1798
|
-
# skills
|
|
1799
|
-
# quality-engineering: { ref: quality-engineering-v1.0.0 }
|
|
1846
|
+
# SDLC support skills such as quality-engineering are included by default.
|
|
1847
|
+
# Specialist definitions are synced separately as native sub-agents, not skills.
|
|
1800
1848
|
#
|
|
1801
1849
|
`;
|
|
1802
1850
|
const configPath = path.default.join(cwd, ".skillsrc");
|
|
@@ -2292,27 +2340,27 @@ var getTargets = (home = os.default.homedir()) => {
|
|
|
2292
2340
|
},
|
|
2293
2341
|
[Agent.Copilot]: {
|
|
2294
2342
|
agent: Agent.Copilot,
|
|
2295
|
-
projectFile: ".
|
|
2343
|
+
projectFile: ".github/mcp.json",
|
|
2296
2344
|
userFile: (() => {
|
|
2297
2345
|
if (process.platform === "darwin") return path.default.join(HOME, "Library", "Application Support", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2298
2346
|
if (process.platform === "win32") return path.default.join(HOME, "AppData", "Roaming", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2299
2347
|
if (process.platform === "linux") return path.default.join(HOME, ".config", "Code", "User", "globalStorage", "github.copilot-chat", "mcp.json");
|
|
2300
2348
|
return null;
|
|
2301
2349
|
})(),
|
|
2302
|
-
key: "
|
|
2350
|
+
key: "mcpServers",
|
|
2303
2351
|
shape: "map"
|
|
2304
2352
|
},
|
|
2305
2353
|
[Agent.OpenCode]: {
|
|
2306
2354
|
agent: Agent.OpenCode,
|
|
2307
|
-
projectFile: "opencode.json",
|
|
2308
|
-
userFile: path.default.join(HOME, ".
|
|
2309
|
-
key: "
|
|
2355
|
+
projectFile: ".opencode/mcp_config.json",
|
|
2356
|
+
userFile: path.default.join(HOME, ".opencode", "mcp_config.json"),
|
|
2357
|
+
key: "mcpServers",
|
|
2310
2358
|
shape: "map"
|
|
2311
2359
|
},
|
|
2312
|
-
[Agent.
|
|
2313
|
-
agent: Agent.
|
|
2314
|
-
projectFile: ".codex/
|
|
2315
|
-
userFile: path.default.join(HOME, ".codex", "
|
|
2360
|
+
[Agent.Codex]: {
|
|
2361
|
+
agent: Agent.Codex,
|
|
2362
|
+
projectFile: ".codex/mcp_config.json",
|
|
2363
|
+
userFile: path.default.join(HOME, ".codex", "mcp_config.json"),
|
|
2316
2364
|
key: "mcpServers",
|
|
2317
2365
|
shape: "map"
|
|
2318
2366
|
}
|
|
@@ -3346,6 +3394,8 @@ var IndexGeneratorServiceImpl = class extends IndexGeneratorService {
|
|
|
3346
3394
|
"",
|
|
3347
3395
|
"| Tool | When to call it |",
|
|
3348
3396
|
"| --- | --- |",
|
|
3397
|
+
"| `list_workflows()` | At the start of any task or session to discover available standard operating procedures |",
|
|
3398
|
+
"| `get_workflow(name)` | Once a relevant workflow is identified to retrieve exact step-by-step instructions |",
|
|
3349
3399
|
"| `load_skills_for_files(files=[...])` | Before editing/reviewing any source file |",
|
|
3350
3400
|
"| `load_skills_for_keywords(keywords=[...])` | Planning before files are chosen |",
|
|
3351
3401
|
"| `get_skill(category, name)` | Direct lookup when you know the skill id |",
|
|
@@ -3868,7 +3918,7 @@ var SpecialistTransformer = class {
|
|
|
3868
3918
|
name: `${baseName}.md`,
|
|
3869
3919
|
content: `---\nname: ${baseName}\ndescription: "${description}"\n---\n\n${body}`
|
|
3870
3920
|
};
|
|
3871
|
-
case Agent.
|
|
3921
|
+
case Agent.Codex: return {
|
|
3872
3922
|
name: `${baseName}.toml`,
|
|
3873
3923
|
content: `name = "${baseName}"\ndescription = "${description}"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\n${body}\n"""`
|
|
3874
3924
|
};
|
|
@@ -3886,6 +3936,56 @@ var SpecialistTransformer = class {
|
|
|
3886
3936
|
* to native agent configuration directories.
|
|
3887
3937
|
*/
|
|
3888
3938
|
var SpecialistSyncService = class {
|
|
3939
|
+
constructor(githubService = new GithubService(process.env.GITHUB_TOKEN)) {
|
|
3940
|
+
this.githubService = githubService;
|
|
3941
|
+
}
|
|
3942
|
+
async assembleSpecialists(config) {
|
|
3943
|
+
const githubMatch = GithubService.parseGitHubUrl(config.registry);
|
|
3944
|
+
if (!githubMatch) return [];
|
|
3945
|
+
const { owner, repo } = githubMatch;
|
|
3946
|
+
const ref = (await this.githubService.getRepoInfo(owner, repo))?.default_branch || "main";
|
|
3947
|
+
const treeData = await this.githubService.getRepoTree(owner, repo, ref);
|
|
3948
|
+
if (!treeData) return [];
|
|
3949
|
+
const skillFiles = treeData.tree.filter((item) => item.type === "blob" && item.path.startsWith("skills/specialists/") && item.path.endsWith("/SKILL.md"));
|
|
3950
|
+
const specialists = [];
|
|
3951
|
+
for (const file of skillFiles) {
|
|
3952
|
+
const specialistName = file.path.split("/")[2];
|
|
3953
|
+
if (!specialistName) continue;
|
|
3954
|
+
const content = await this.githubService.getRawFile(owner, repo, ref, file.path);
|
|
3955
|
+
if (!content) continue;
|
|
3956
|
+
specialists.push({
|
|
3957
|
+
category: "specialists",
|
|
3958
|
+
skill: specialistName,
|
|
3959
|
+
files: [{
|
|
3960
|
+
name: "SKILL.md",
|
|
3961
|
+
content
|
|
3962
|
+
}]
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
return specialists;
|
|
3966
|
+
}
|
|
3967
|
+
async syncCollectedSpecialists(rootDir, agents, specialists) {
|
|
3968
|
+
if (specialists.length === 0) return;
|
|
3969
|
+
for (const agentId of agents) {
|
|
3970
|
+
const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
|
|
3971
|
+
if (!agentDef || !agentDef.agentPath) continue;
|
|
3972
|
+
const targetDir = path.default.join(rootDir, agentDef.agentPath);
|
|
3973
|
+
await fs_extra.default.ensureDir(targetDir);
|
|
3974
|
+
let syncedCount = 0;
|
|
3975
|
+
for (const specialist of specialists) {
|
|
3976
|
+
const skillFile = specialist.files.find((file) => file.name === "SKILL.md");
|
|
3977
|
+
if (!skillFile) continue;
|
|
3978
|
+
const transformed = SpecialistTransformer.transform({
|
|
3979
|
+
name: specialist.skill,
|
|
3980
|
+
content: skillFile.content
|
|
3981
|
+
}, agentId);
|
|
3982
|
+
if (!transformed) continue;
|
|
3983
|
+
await fs_extra.default.outputFile(path.default.join(targetDir, transformed.name), transformed.content);
|
|
3984
|
+
syncedCount++;
|
|
3985
|
+
}
|
|
3986
|
+
if (syncedCount > 0) console.log(picocolors.default.green(` ✅ ${syncedCount} specialists synced to ${agentDef.agentPath}/ (${agentDef.name})`));
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3889
3989
|
/**
|
|
3890
3990
|
* Syncs all specialists from a source directory to active agents.
|
|
3891
3991
|
* @param rootDir Project root directory
|
|
@@ -3898,28 +3998,21 @@ var SpecialistSyncService = class {
|
|
|
3898
3998
|
const specialistFolders = (await fs_extra.default.readdir(specialistsDir)).filter((f) => {
|
|
3899
3999
|
return fs_extra.default.statSync(path.default.join(specialistsDir, f)).isDirectory();
|
|
3900
4000
|
});
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
await fs_extra.default.
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
const transformed = SpecialistTransformer.transform({
|
|
3912
|
-
name: folder,
|
|
4001
|
+
const collected = [];
|
|
4002
|
+
for (const folder of specialistFolders) {
|
|
4003
|
+
const skillPath = path.default.join(specialistsDir, folder, "SKILL.md");
|
|
4004
|
+
if (!await fs_extra.default.pathExists(skillPath)) continue;
|
|
4005
|
+
const content = await fs_extra.default.readFile(skillPath, "utf8");
|
|
4006
|
+
collected.push({
|
|
4007
|
+
category: "specialists",
|
|
4008
|
+
skill: folder,
|
|
4009
|
+
files: [{
|
|
4010
|
+
name: "SKILL.md",
|
|
3913
4011
|
content
|
|
3914
|
-
}
|
|
3915
|
-
|
|
3916
|
-
const targetFile = path.default.join(targetDir, transformed.name);
|
|
3917
|
-
await fs_extra.default.outputFile(targetFile, transformed.content);
|
|
3918
|
-
syncedCount++;
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
if (syncedCount > 0) console.log(picocolors.default.green(` ✅ ${syncedCount} specialists synced to ${agentDef.agentPath}/ (${agentDef.name})`));
|
|
4012
|
+
}]
|
|
4013
|
+
});
|
|
3922
4014
|
}
|
|
4015
|
+
return this.syncCollectedSpecialists(rootDir, agents, collected);
|
|
3923
4016
|
}
|
|
3924
4017
|
};
|
|
3925
4018
|
//#endregion
|
|
@@ -4075,7 +4168,8 @@ var SyncService = class {
|
|
|
4075
4168
|
}
|
|
4076
4169
|
async assembleSkills(categories, config) {
|
|
4077
4170
|
await this.warnIfSyncingFromSameRepo(config);
|
|
4078
|
-
|
|
4171
|
+
const skillCategories = categories.filter((category) => category !== "specialists");
|
|
4172
|
+
return this.skillSyncService.assembleSkills(skillCategories, config);
|
|
4079
4173
|
}
|
|
4080
4174
|
async warnIfSyncingFromSameRepo(config) {
|
|
4081
4175
|
const remoteUrl = this.gitService.getRemoteUrl(process.cwd());
|
|
@@ -4102,10 +4196,10 @@ var SyncService = class {
|
|
|
4102
4196
|
async syncSpecialists(config) {
|
|
4103
4197
|
const agents = await this.resolveTargetAgents(config);
|
|
4104
4198
|
if (agents.length === 0) return;
|
|
4105
|
-
const
|
|
4106
|
-
|
|
4107
|
-
const
|
|
4108
|
-
return this.specialistSyncService.
|
|
4199
|
+
const localRegistrySource = path.default.join(process.cwd(), "skills/specialists");
|
|
4200
|
+
if (await fs_extra.default.pathExists(localRegistrySource)) return this.specialistSyncService.syncSpecialists(process.cwd(), agents, localRegistrySource);
|
|
4201
|
+
const specialists = await this.specialistSyncService.assembleSpecialists(config);
|
|
4202
|
+
return this.specialistSyncService.syncCollectedSpecialists(process.cwd(), agents, specialists);
|
|
4109
4203
|
}
|
|
4110
4204
|
async applyIndices(config, targetAgents) {
|
|
4111
4205
|
const agents = targetAgents || await this.resolveTargetAgents(config);
|
|
@@ -4368,6 +4462,14 @@ var SyncCommand = class {
|
|
|
4368
4462
|
async runHookPhase(config) {
|
|
4369
4463
|
const agents = config.agents ?? [];
|
|
4370
4464
|
if (agents.length === 0) return;
|
|
4465
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
4466
|
+
if (!mcp.enabled || mcp.scope === "disabled") {
|
|
4467
|
+
await this.hookService.uninstall({
|
|
4468
|
+
rootDir: process.cwd(),
|
|
4469
|
+
agents
|
|
4470
|
+
});
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4371
4473
|
const writes = (await this.hookService.install({
|
|
4372
4474
|
rootDir: process.cwd(),
|
|
4373
4475
|
agents
|
|
@@ -4382,7 +4484,7 @@ var SyncCommand = class {
|
|
|
4382
4484
|
};
|
|
4383
4485
|
var package_default = {
|
|
4384
4486
|
name: "agent-skills-standard",
|
|
4385
|
-
version: "2.
|
|
4487
|
+
version: "2.4.1",
|
|
4386
4488
|
description: "A CLI to manage and sync AI agent skills standard for Cursor, Claude, Copilot, Windsurf, and more.",
|
|
4387
4489
|
main: "dist/index.js",
|
|
4388
4490
|
bin: {
|
|
@@ -4868,7 +4970,7 @@ var ValidateCommand = class {
|
|
|
4868
4970
|
//#region src/index.ts
|
|
4869
4971
|
dotenv.default.config();
|
|
4870
4972
|
var program = new commander.Command();
|
|
4871
|
-
program.name("agent-skills-standard").description("A CLI to manage and sync AI agent skills for Cursor, Claude, Copilot, Windsurf, and more.").version("2.
|
|
4973
|
+
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.1");
|
|
4872
4974
|
program.command("init").description("Initialize a .skillsrc configuration file interactively").action(async () => {
|
|
4873
4975
|
await new InitCommand().run();
|
|
4874
4976
|
});
|