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/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
  };
@@ -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.array(zod.z.nativeEnum(Agent)).optional(),
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
- * 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.
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 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)
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
- file_path = data.get("tool_input", {}).get("file_path", "")
1190
- if not file_path or _should_skip(file_path):
1191
- sys.exit(0)
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
- 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
- )
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
- sys.exit(0)
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
- if __name__ == "__main__":
1204
- main()
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) switch (agent) {
1251
- case Agent.Claude:
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
- break;
1257
- default: report.unsupported.push(agent);
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) 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: {
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
- break;
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) 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: {
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
- break;
1344
+ continue;
1308
1345
  }
1309
- default: break;
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 installClaudeHook(rootDir, report) {
1314
- const scriptAbs = path.default.join(rootDir, CLAUDE_HOOK_SCRIPT_REL);
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, CLAUDE_SKILL_LOADER_PY);
1319
- report.writes.push({
1320
- agent: Agent.Claude,
1321
- file: CLAUDE_HOOK_SCRIPT_REL,
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: Agent.Claude,
1326
- file: CLAUDE_HOOK_SCRIPT_REL,
1364
+ } else opts.report.writes.push({
1365
+ agent: opts.agent,
1366
+ file: opts.scriptRelPath,
1327
1367
  action: "skipped-existing"
1328
1368
  });
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);
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: CLAUDE_HOOK_CMD,
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(settingsAbs, existing);
1354
- report.writes.push({
1355
- agent: Agent.Claude,
1356
- file: CLAUDE_SETTINGS_REL,
1357
- action: settingsExists ? "updated" : "added"
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: Agent.Claude,
1361
- file: CLAUDE_SETTINGS_REL,
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 uninstallClaudeHook(rootDir) {
1405
+ async uninstallStandardHookConfig(rootDir, configRelPath) {
1366
1406
  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);
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(settingsAbs, data);
1386
- removed.push(CLAUDE_SETTINGS_REL);
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
- # TIP: For Jira/Zephyr automation and requirement analysis, manually add:
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: ".vscode/mcp.json",
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: "servers",
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, ".config", "opencode", "opencode.json"),
2309
- key: "mcp",
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.OpenAI]: {
2313
- agent: Agent.OpenAI,
2314
- projectFile: ".codex/hooks.json",
2315
- userFile: path.default.join(HOME, ".codex", "hooks.json"),
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.OpenAI: return {
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
- for (const agentId of agents) {
3902
- const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
3903
- if (!agentDef || !agentDef.agentPath) continue;
3904
- const targetDir = path.default.join(rootDir, agentDef.agentPath);
3905
- await fs_extra.default.ensureDir(targetDir);
3906
- let syncedCount = 0;
3907
- for (const folder of specialistFolders) {
3908
- const skillPath = path.default.join(specialistsDir, folder, "SKILL.md");
3909
- if (!await fs_extra.default.pathExists(skillPath)) continue;
3910
- const content = await fs_extra.default.readFile(skillPath, "utf8");
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
- }, agentId);
3915
- if (transformed) {
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
- return this.skillSyncService.assembleSkills(categories, config);
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 primaryAgent = SUPPORTED_AGENTS.find((a) => a.id === agents[0]);
4106
- const sourceDir = primaryAgent ? path.default.join(process.cwd(), primaryAgent.path, "specialists") : path.default.join(process.cwd(), "skills/specialists");
4107
- const finalSourceDir = await fs_extra.default.pathExists(sourceDir) ? sourceDir : path.default.join(process.cwd(), "skills/specialists");
4108
- return this.specialistSyncService.syncSpecialists(process.cwd(), agents, finalSourceDir);
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.3.0",
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.3.0");
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
  });