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/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["OpenAI"] = "openai";
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.OpenAI: return {
761
+ case Agent.Codex: return {
745
762
  id,
746
- name: "OpenAI",
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
- * Claude Code PreToolUse hook script (Python).
1128
- * Fires before every Edit/Write — emits a load_skills_for_files() reminder as
1129
- * prompt context so Claude always consults skill rules before editing.
1130
- * Always exits 0 — never blocks tool execution.
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 CLAUDE_SKILL_LOADER_PY = `#!/usr/bin/env python3
1133
- """PreToolUse hook: remind Claude to call load_skills_for_files() before any code edit.
1134
-
1135
- Installed by agent-skills-standard (ags sync). Remove via: ags hooks uninstall
1136
-
1137
- Why a hook (not just AGENTS.md or the MCP alone):
1138
- Context compaction silently drops the check-AGENTS.md instruction.
1139
- The MCP is on-demand - Claude must remember to call it.
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
- file_path = data.get("tool_input", {}).get("file_path", "")
1190
- if not file_path or _should_skip(file_path):
1191
- sys.exit(0)
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
- print(
1194
- f"[SKILL TRIGGER] Editing: {Path(file_path).name}\\n"
1195
- f"-> Call load_skills_for_files(files=[ \\"{file_path}\\" ]) on the "
1196
- f"agent-skills-standard MCP. It returns applicable SKILL.md rules, "
1197
- f"or nothing if no skills match this file type."
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
- sys.exit(0)
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
- if __name__ == "__main__":
1204
- main()
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) switch (agent) {
1251
- case Agent.Claude:
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
- break;
1257
- default: report.unsupported.push(agent);
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) switch (agent) {
1264
- case Agent.Claude: {
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
- break;
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) switch (agent) {
1290
- case Agent.Claude: {
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
- break;
1327
+ continue;
1308
1328
  }
1309
- default: break;
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 installClaudeHook(rootDir, report) {
1314
- const scriptAbs = path.default.join(rootDir, CLAUDE_HOOK_SCRIPT_REL);
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, CLAUDE_SKILL_LOADER_PY);
1319
- report.writes.push({
1320
- agent: Agent.Claude,
1321
- file: CLAUDE_HOOK_SCRIPT_REL,
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: Agent.Claude,
1326
- file: CLAUDE_HOOK_SCRIPT_REL,
1347
+ } else opts.report.writes.push({
1348
+ agent: opts.agent,
1349
+ file: opts.scriptRelPath,
1327
1350
  action: "skipped-existing"
1328
1351
  });
1329
- const settingsAbs = path.default.join(rootDir, CLAUDE_SETTINGS_REL);
1330
- const settingsExists = await fs_extra.default.pathExists(settingsAbs);
1331
- const existing = await this.readJson(settingsAbs);
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: CLAUDE_HOOK_CMD,
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(settingsAbs, existing);
1354
- report.writes.push({
1355
- agent: Agent.Claude,
1356
- file: CLAUDE_SETTINGS_REL,
1357
- action: settingsExists ? "updated" : "added"
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: Agent.Claude,
1361
- file: CLAUDE_SETTINGS_REL,
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 uninstallClaudeHook(rootDir) {
1388
+ async uninstallStandardHookConfig(rootDir, configRelPath) {
1366
1389
  const removed = [];
1367
- const settingsAbs = path.default.join(rootDir, CLAUDE_SETTINGS_REL);
1368
- if (!await fs_extra.default.pathExists(settingsAbs)) return removed;
1369
- const data = await this.readJson(settingsAbs);
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(settingsAbs, data);
1386
- removed.push(CLAUDE_SETTINGS_REL);
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
- # TIP: For Jira/Zephyr automation and requirement analysis, manually add:
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: ".vscode/mcp.json",
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: "servers",
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, ".config", "opencode", "opencode.json"),
2309
- key: "mcp",
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.OpenAI]: {
2313
- agent: Agent.OpenAI,
2314
- projectFile: ".codex/hooks.json",
2315
- userFile: path.default.join(HOME, ".codex", "hooks.json"),
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
- return `# ${name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")} Skill
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
- > ${description}
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.OpenAI: return {
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
- for (const agentId of agents) {
3890
- const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
3891
- if (!agentDef || !agentDef.agentPath) continue;
3892
- const targetDir = path.default.join(rootDir, agentDef.agentPath);
3893
- await fs_extra.default.ensureDir(targetDir);
3894
- let syncedCount = 0;
3895
- for (const folder of specialistFolders) {
3896
- const skillPath = path.default.join(specialistsDir, folder, "SKILL.md");
3897
- if (!await fs_extra.default.pathExists(skillPath)) continue;
3898
- const content = await fs_extra.default.readFile(skillPath, "utf8");
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
- }, agentId);
3903
- if (transformed) {
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
- return this.skillSyncService.assembleSkills(categories, config);
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 primaryAgent = SUPPORTED_AGENTS.find((a) => a.id === agents[0]);
4094
- const sourceDir = primaryAgent ? path.default.join(process.cwd(), primaryAgent.path, "specialists") : path.default.join(process.cwd(), "skills/specialists");
4095
- const finalSourceDir = await fs_extra.default.pathExists(sourceDir) ? sourceDir : path.default.join(process.cwd(), "skills/specialists");
4096
- return this.specialistSyncService.syncSpecialists(process.cwd(), agents, finalSourceDir);
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.2.5",
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.2.5");
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
  });