agent-skills-standard 2.6.4 → 2.6.6

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 CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  **Sync 242 AI coding standards to any project in one command.** Works with Cursor, Claude Code, GitHub Copilot, Gemini, Windsurf, Trae, Kiro, and Roo.
7
7
 
8
- **Current release:** `v2.6.4` — skill validator accepts package directories under `scripts/`; SkillSpector security scanning, pull request evaluation gates, and verified release tags.
8
+ **Current release:** `v2.6.5` — `ags sync` manifest-check fix for repo-root LICENSE/NOTICE; skill validator accepts package directories under `scripts/`; SkillSpector security scanning, pull request evaluation gates, and verified release tags.
9
9
 
10
10
  ```bash
11
11
  npx agent-skills-standard@latest init # detect your stack
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ let crypto = require("crypto");
50
50
  crypto = __toESM(crypto);
51
51
  var package_default = {
52
52
  name: "agent-skills-standard",
53
- version: "2.6.4",
53
+ version: "2.6.6",
54
54
  description: "A CLI to manage and sync AI agent skills standard for Cursor, Claude, Copilot, Windsurf, and more.",
55
55
  main: "dist/index.js",
56
56
  bin: {
@@ -94,7 +94,7 @@ var package_default = {
94
94
  dependencies: {
95
95
  "commander": "^15.0.0",
96
96
  "cross-fetch": "^4.1.0",
97
- "dotenv": "^18.0.3",
97
+ "dotenv": "^18.0.5",
98
98
  "fs-extra": "^11.4.1",
99
99
  "inquirer": "^14.2.2",
100
100
  "js-yaml": "^5.4.2",
@@ -102,9 +102,9 @@ var package_default = {
102
102
  "zod": "^4.6.5"
103
103
  },
104
104
  devDependencies: {
105
- "@vitest/coverage-v8": "^5.0.1",
106
- "vite": "^8.3.0",
107
- "vitest": "^5.0.1"
105
+ "@vitest/coverage-v8": "^5.0.3",
106
+ "vite": "^8.3.2",
107
+ "vitest": "^5.0.3"
108
108
  }
109
109
  };
110
110
  //#endregion
@@ -2698,7 +2698,7 @@ var McpConfigService = class {
2698
2698
  buildEntry(version) {
2699
2699
  return {
2700
2700
  command: "npx",
2701
- args: ["-y", `${PACKAGE}@${version || "0.6.0"}`]
2701
+ args: ["-y", `${PACKAGE}@${version || "0.6.1"}`]
2702
2702
  };
2703
2703
  }
2704
2704
  /** For testing: allows overriding the home directory. */
@@ -4409,9 +4409,46 @@ async function removeEmptyParents(rootDir, dir) {
4409
4409
  }
4410
4410
  }
4411
4411
  //#endregion
4412
+ //#region src/services/install/safePath.ts
4413
+ /**
4414
+ * Asserts that a target file path resides inside rootDir and that NO intermediate
4415
+ * directory or terminal file under rootDir is a symbolic link.
4416
+ *
4417
+ * Requirements:
4418
+ * - rootDir may itself be a symlink (or inside one); components under rootDir must NOT be.
4419
+ * - Both existing files and dangling symlinks must be detected via lstat.
4420
+ * - Fails closed if lexical containment is breached or if any component under rootDir is a symlink.
4421
+ *
4422
+ * @param rootDir The trusted root directory
4423
+ * @param targetPath The destination file/directory path to check
4424
+ */
4425
+ async function assertNoSymlinksUnderRoot(rootDir, targetPath) {
4426
+ const resolvedRoot = path.default.resolve(rootDir);
4427
+ const resolvedTarget = path.default.resolve(targetPath);
4428
+ const relative = path.default.relative(resolvedRoot, resolvedTarget);
4429
+ if (relative === ".." || relative.startsWith(`..${path.default.sep}`) || path.default.isAbsolute(relative)) throw new Error(`Path traversal or outside target detected: "${targetPath}" is outside "${rootDir}"`);
4430
+ const segments = relative.split(path.default.sep).filter(Boolean);
4431
+ let current = resolvedRoot;
4432
+ for (const segment of segments) {
4433
+ current = path.default.join(current, segment);
4434
+ try {
4435
+ if ((await fs_extra.default.lstat(current)).isSymbolicLink()) throw new Error(`Symlink path component not permitted: "${current}" is a symbolic link under "${rootDir}"`);
4436
+ } catch (err) {
4437
+ if (err instanceof Error && err.message.includes("Symlink path component")) throw err;
4438
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") break;
4439
+ throw err;
4440
+ }
4441
+ }
4442
+ }
4443
+ //#endregion
4412
4444
  //#region src/services/install/OwnershipWriter.ts
4413
4445
  var PassthroughWriter = class {
4446
+ rootDir;
4447
+ constructor(rootDir) {
4448
+ this.rootDir = rootDir;
4449
+ }
4414
4450
  async write(absPath, content) {
4451
+ await assertNoSymlinksUnderRoot(this.rootDir, absPath);
4415
4452
  await fs_extra.default.outputFile(absPath, content);
4416
4453
  }
4417
4454
  };
@@ -4434,6 +4471,7 @@ var OwnershipWriter = class {
4434
4471
  this.opts = opts;
4435
4472
  }
4436
4473
  async write(absPath, content, meta) {
4474
+ await assertNoSymlinksUnderRoot(this.opts.rootDir, absPath);
4437
4475
  const rel = toPosixRel(this.opts.rootDir, absPath);
4438
4476
  this.touched.add(rel);
4439
4477
  const incoming = sha256(content);
@@ -4489,6 +4527,7 @@ var OwnershipWriter = class {
4489
4527
  continue;
4490
4528
  }
4491
4529
  const abs = path.default.join(this.opts.rootDir, rel);
4530
+ await assertNoSymlinksUnderRoot(this.opts.rootDir, abs);
4492
4531
  if (!await fs_extra.default.pathExists(abs)) continue;
4493
4532
  if (sha256(await fs_extra.default.readFile(abs)) !== prev.sha256) {
4494
4533
  this.plan.keptOrphans.push(rel);
@@ -4537,7 +4576,7 @@ var AgentBridgeService = class {
4537
4576
  * @param rootDir Project root directory
4538
4577
  * @param agents List of agents to generate rules for
4539
4578
  */
4540
- async bridge(rootDir, agents, writer = new PassthroughWriter(), options = {}) {
4579
+ async bridge(rootDir, agents, writer = new PassthroughWriter(rootDir), options = {}) {
4541
4580
  const fileNameBase = "agent-skill-standard-rule";
4542
4581
  const commonDescription = "Rule for Agent Skills Standard - Always consult AGENTS.md for consolidated project context and technical triggers.";
4543
4582
  for (const agentId of agents) {
@@ -4575,7 +4614,9 @@ var AgentBridgeService = class {
4575
4614
  "",
4576
4615
  "### State 4: Execution & Audit Log (Output Generation)",
4577
4616
  "",
4578
- "- **Action:** You may now begin answering the user or writing code.",
4617
+ "- **Action:**",
4618
+ " - **Multi-task implementation plan (new or existing):** Do NOT edit files directly in the orchestrator session. Present the execution confirmation gate (Subagent-Driven vs. Inline) and use `subagent-driven-development`.",
4619
+ " - **Analysis, review, or single-file fix:** You may begin answering the user or implementing.",
4579
4620
  "- **Constraint:** Your very first text output MUST be a \"Pre-Write Audit Log\" confirming which skills were loaded, or explicitly stating \"No project-specific skills applicable.\"",
4580
4621
  "",
4581
4622
  "## Self-Learning Protocol",
@@ -5198,17 +5239,22 @@ var SkillSyncService = class {
5198
5239
  /**
5199
5240
  * Writes collected skills to target agent paths.
5200
5241
  */
5201
- async writeSkills(skills, config, agents, writer = new PassthroughWriter()) {
5242
+ async writeSkills(skills, config, agents, writer = new PassthroughWriter(process.cwd()), rootDir = process.cwd()) {
5202
5243
  const overrides = config.custom_overrides || [];
5244
+ for (const agentId of agents) {
5245
+ const basePath = SUPPORTED_AGENTS.find((agent) => agent.id === agentId)?.path;
5246
+ if (!basePath) continue;
5247
+ for (const skill of skills) await this.writeSkillForAgent(agentId, skill, overrides, basePath, writer, config, true, rootDir);
5248
+ }
5203
5249
  for (const agentId of agents) {
5204
5250
  const agentDef = SUPPORTED_AGENTS.find((agent) => agent.id === agentId);
5205
5251
  if (!agentDef?.path) continue;
5206
5252
  const basePath = agentDef.path;
5207
- for (const skill of skills) await this.writeSkillForAgent(agentId, skill, overrides, basePath, writer, config);
5253
+ for (const skill of skills) await this.writeSkillForAgent(agentId, skill, overrides, basePath, writer, config, false, rootDir);
5208
5254
  console.log(picocolors.default.gray(` - Updated ${basePath}/ (${agentDef.name})`));
5209
5255
  }
5210
5256
  }
5211
- async writeSkillForAgent(agentId, skill, overrides, basePath, writer, config) {
5257
+ async writeSkillForAgent(agentId, skill, overrides, basePath, writer, config, preflightOnly = false, rootDir = basePath) {
5212
5258
  const isKiro = agentId === Agent.Kiro;
5213
5259
  const skillPath = isKiro ? path.default.join(basePath, `${skill.category}-${skill.skill}`) : path.default.join(basePath, skill.category, skill.skill);
5214
5260
  if (!this.isPathSafe(skillPath, basePath)) {
@@ -5216,7 +5262,7 @@ var SkillSyncService = class {
5216
5262
  throw new Error(`Invalid skill path ${skill.category}/${skill.skill}`);
5217
5263
  }
5218
5264
  const source = `skill:${skill.category}/${skill.skill}@${config.skills?.[skill.category]?.ref ?? "main"}`;
5219
- const pending = [];
5265
+ const pending = preflightOnly ? null : [];
5220
5266
  for (const fileItem of skill.files) {
5221
5267
  const targetFilePath = path.default.join(skillPath, fileItem.name);
5222
5268
  if (!this.isPathSafe(targetFilePath, skillPath)) {
@@ -5227,12 +5273,15 @@ var SkillSyncService = class {
5227
5273
  console.log(picocolors.default.yellow(` ⚠️ Skipping overridden: ${this.normalizePath(targetFilePath)}`));
5228
5274
  continue;
5229
5275
  }
5276
+ await assertNoSymlinksUnderRoot(rootDir, targetFilePath);
5277
+ if (pending === null) continue;
5230
5278
  const content = isKiro && fileItem.name === "SKILL.md" ? this.transformSkillForKiro(fileItem.content, skill.category) : fileItem.bytes ?? fileItem.content;
5231
5279
  pending.push({
5232
5280
  target: targetFilePath,
5233
5281
  content
5234
5282
  });
5235
5283
  }
5284
+ if (pending === null) return;
5236
5285
  for (const { target, content } of pending) await writer.write(target, content, {
5237
5286
  owner: "skill",
5238
5287
  source,
@@ -5255,6 +5304,7 @@ var SkillSyncService = class {
5255
5304
  const { ok: files, failed } = await this.githubService.downloadFilesConcurrentBytes(downloadTasks);
5256
5305
  const verifier = verifiers[sourceCat] ?? verifiers[category];
5257
5306
  if (verifier) for (const f of files) {
5307
+ if (this.isRootAttributionFile(f.path)) continue;
5258
5308
  const rejection = verifier.check(f.path, f.content);
5259
5309
  if (rejection) failed.push({
5260
5310
  path: f.path,
@@ -5649,7 +5699,7 @@ var WorkflowSyncService = class {
5649
5699
  * - Copilot: prompt files
5650
5700
  * - Cursor/Trae/Codex: skill folders with SKILL.md
5651
5701
  */
5652
- async writeWorkflows(workflows, config, agents, writer = new PassthroughWriter()) {
5702
+ async writeWorkflows(workflows, config, agents, writer = new PassthroughWriter(process.cwd())) {
5653
5703
  if (workflows.length === 0) return;
5654
5704
  const overrides = config.custom_overrides || [];
5655
5705
  const targetAgents = agents || [Agent.Antigravity];
@@ -5737,10 +5787,8 @@ function summarizeDeclaredPermissions(metadata) {
5737
5787
  }
5738
5788
  /**
5739
5789
  * For platforms with no way to express a declared risk_tier/permissions/
5740
- * allowed-tools, prepend a visible warning rather than silently dropping
5741
- * the declaration — a persona authored as restricted for one platform
5742
- * should not read as unconstrained everywhere else without at least a
5743
- * trace of what was intended.
5790
+ * allowed-tools, emit a visible warning after native frontmatter rather than
5791
+ * silently dropping the declaration.
5744
5792
  */
5745
5793
  function unenforceableWarningComment(metadata, agentName) {
5746
5794
  const declared = summarizeDeclaredPermissions(metadata);
@@ -5780,39 +5828,39 @@ var SpecialistTransformer = class {
5780
5828
  }
5781
5829
  case "cursor-mdc": return {
5782
5830
  name: `${fullName}.mdc`,
5783
- content: `${unenforceableWarningComment(metadata, "Cursor")}---\n${dumpFrontmatter({
5831
+ content: `---\n${dumpFrontmatter({
5784
5832
  description,
5785
5833
  globs: ["**/*"]
5786
- })}\n---\n# Specialist: ${baseName}\n\n${body}`
5834
+ })}\n---\n${unenforceableWarningComment(metadata, "Cursor")}\n# Specialist: ${baseName}\n\n${body}`
5787
5835
  };
5788
5836
  case "copilot-instructions": return {
5789
5837
  name: `${fullName}.instructions.md`,
5790
- content: `${unenforceableWarningComment(metadata, "Copilot")}---\n${dumpFrontmatter({
5838
+ content: `---\n${dumpFrontmatter({
5791
5839
  description,
5792
5840
  applyTo: "**/*"
5793
- })}\n---\n\n${body}`
5841
+ })}\n---\n${unenforceableWarningComment(metadata, "Copilot")}\n${body}`
5794
5842
  };
5795
5843
  case "opencode-md": return {
5796
5844
  name: `${baseName}.md`,
5797
- content: `${unenforceableWarningComment(metadata, "OpenCode")}---\n${dumpFrontmatter({
5845
+ content: `---\n${dumpFrontmatter({
5798
5846
  description,
5799
5847
  mode: "subagent"
5800
- })}\n---\n\n${body}`
5848
+ })}\n---\n${unenforceableWarningComment(metadata, "OpenCode")}\n${body}`
5801
5849
  };
5802
5850
  case "gemini-md": return {
5803
5851
  name: `${baseName}.md`,
5804
- content: `${unenforceableWarningComment(metadata, "Gemini")}---\n${dumpFrontmatter({
5852
+ content: `---\n${dumpFrontmatter({
5805
5853
  name: baseName,
5806
5854
  description,
5807
5855
  kind: "local"
5808
- })}\n---\n\n${body}`
5856
+ })}\n---\n${unenforceableWarningComment(metadata, "Gemini")}\n${body}`
5809
5857
  };
5810
5858
  case "kiro-md": return {
5811
5859
  name: `${baseName}.md`,
5812
- content: `${unenforceableWarningComment(metadata, "Kiro")}---\n${dumpFrontmatter({
5860
+ content: `---\n${dumpFrontmatter({
5813
5861
  name: baseName,
5814
5862
  description
5815
- })}\n---\n\n${body}`
5863
+ })}\n---\n${unenforceableWarningComment(metadata, "Kiro")}\n${body}`
5816
5864
  };
5817
5865
  case "codex-toml": {
5818
5866
  const sandboxMode = sandboxModeFor(metadata.risk_tier);
@@ -5874,7 +5922,7 @@ var SpecialistSyncService = class {
5874
5922
  }
5875
5923
  return specialists;
5876
5924
  }
5877
- async syncCollectedSpecialists(rootDir, agents, specialists, writer = new PassthroughWriter()) {
5925
+ async syncCollectedSpecialists(rootDir, agents, specialists, writer = new PassthroughWriter(rootDir)) {
5878
5926
  if (specialists.length === 0) return 0;
5879
5927
  for (const agentId of agents) {
5880
5928
  const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
@@ -5906,7 +5954,7 @@ var SpecialistSyncService = class {
5906
5954
  * @param agents List of agents to sync for
5907
5955
  * @param sourceDir Optional custom source directory for specialists
5908
5956
  */
5909
- async syncSpecialists(rootDir, agents, sourceDir, writer = new PassthroughWriter()) {
5957
+ async syncSpecialists(rootDir, agents, sourceDir, writer = new PassthroughWriter(rootDir)) {
5910
5958
  const specialistsDir = sourceDir || path.default.join(rootDir, "skills/specialists");
5911
5959
  if (!await fs_extra.default.pathExists(specialistsDir)) return 0;
5912
5960
  const specialistFolders = (await fs_extra.default.readdir(specialistsDir)).filter((f) => {
@@ -6085,8 +6133,10 @@ var BackupSession = class {
6085
6133
  const normalized = rel.replace(/\\/g, "/");
6086
6134
  if (this.added.has(normalized)) return;
6087
6135
  const src = path.default.join(this.rootDir, normalized);
6136
+ await assertNoSymlinksUnderRoot(this.rootDir, src);
6088
6137
  if (!await fs_extra.default.pathExists(src)) return;
6089
6138
  const dest = path.default.join(this.backupDir, "files", normalized);
6139
+ await assertNoSymlinksUnderRoot(this.rootDir, dest);
6090
6140
  await fs_extra.default.copy(src, dest, { overwrite: true });
6091
6141
  this.added.add(normalized);
6092
6142
  }
@@ -6097,7 +6147,9 @@ var BackupSession = class {
6097
6147
  reason: this.reason,
6098
6148
  files: Array.from(this.added)
6099
6149
  };
6100
- await fs_extra.default.outputJson(path.default.join(this.backupDir, "manifest.json"), manifest, { spaces: 2 });
6150
+ const manifestPath = path.default.join(this.backupDir, "manifest.json");
6151
+ await assertNoSymlinksUnderRoot(this.rootDir, manifestPath);
6152
+ await fs_extra.default.outputJson(manifestPath, manifest, { spaces: 2 });
6101
6153
  const backupsDir = path.default.dirname(this.backupDir);
6102
6154
  if (await fs_extra.default.pathExists(backupsDir)) {
6103
6155
  const dirs = (await fs_extra.default.readdir(backupsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -6133,12 +6185,15 @@ var BackupService = class {
6133
6185
  }
6134
6186
  async begin(rootDir, reason) {
6135
6187
  const agsDir = path.default.join(rootDir, ".ags");
6188
+ await assertNoSymlinksUnderRoot(rootDir, agsDir);
6136
6189
  await fs_extra.default.ensureDir(agsDir);
6137
6190
  const gitignorePath = path.default.join(agsDir, ".gitignore");
6191
+ await assertNoSymlinksUnderRoot(rootDir, gitignorePath);
6138
6192
  const defaultGitignore = "backups/\npolicy-candidates.json\n";
6139
6193
  if (!await fs_extra.default.pathExists(gitignorePath)) await fs_extra.default.writeFile(gitignorePath, defaultGitignore, "utf8");
6140
6194
  else if (await fs_extra.default.readFile(gitignorePath, "utf8") === "*\n") await fs_extra.default.writeFile(gitignorePath, defaultGitignore, "utf8");
6141
6195
  const backupsDir = path.default.join(agsDir, "backups");
6196
+ await assertNoSymlinksUnderRoot(rootDir, backupsDir);
6142
6197
  await fs_extra.default.ensureDir(backupsDir);
6143
6198
  const date = this.now();
6144
6199
  const baseId = this.formatId(date);
@@ -6149,6 +6204,7 @@ var BackupService = class {
6149
6204
  counter++;
6150
6205
  }
6151
6206
  const backupDir = path.default.join(backupsDir, id);
6207
+ await assertNoSymlinksUnderRoot(rootDir, backupDir);
6152
6208
  await fs_extra.default.ensureDir(backupDir);
6153
6209
  return new BackupSession(id, rootDir, backupDir, reason, date.toISOString(), this.keep);
6154
6210
  }
@@ -6178,6 +6234,7 @@ var BackupService = class {
6178
6234
  const src = path.default.join(backupDir, "files", rel);
6179
6235
  if (await fs_extra.default.pathExists(src)) {
6180
6236
  const dest = path.default.join(rootDir, rel);
6237
+ await assertNoSymlinksUnderRoot(rootDir, dest);
6181
6238
  await fs_extra.default.copy(src, dest, { overwrite: true });
6182
6239
  restored.push(rel);
6183
6240
  }
@@ -6322,7 +6379,7 @@ var SyncService = class {
6322
6379
  specialistSyncService = new SpecialistSyncService();
6323
6380
  gitService = new GitService();
6324
6381
  lockfileService = new LockfileService();
6325
- writer = new PassthroughWriter();
6382
+ writer = new PassthroughWriter(process.cwd());
6326
6383
  ownership = null;
6327
6384
  dryRun = false;
6328
6385
  cachedRegistryMetadata = void 0;