agent-skills-standard 2.6.5 → 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/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.5",
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,
@@ -5650,7 +5699,7 @@ var WorkflowSyncService = class {
5650
5699
  * - Copilot: prompt files
5651
5700
  * - Cursor/Trae/Codex: skill folders with SKILL.md
5652
5701
  */
5653
- async writeWorkflows(workflows, config, agents, writer = new PassthroughWriter()) {
5702
+ async writeWorkflows(workflows, config, agents, writer = new PassthroughWriter(process.cwd())) {
5654
5703
  if (workflows.length === 0) return;
5655
5704
  const overrides = config.custom_overrides || [];
5656
5705
  const targetAgents = agents || [Agent.Antigravity];
@@ -5738,10 +5787,8 @@ function summarizeDeclaredPermissions(metadata) {
5738
5787
  }
5739
5788
  /**
5740
5789
  * For platforms with no way to express a declared risk_tier/permissions/
5741
- * allowed-tools, prepend a visible warning rather than silently dropping
5742
- * the declaration — a persona authored as restricted for one platform
5743
- * should not read as unconstrained everywhere else without at least a
5744
- * trace of what was intended.
5790
+ * allowed-tools, emit a visible warning after native frontmatter rather than
5791
+ * silently dropping the declaration.
5745
5792
  */
5746
5793
  function unenforceableWarningComment(metadata, agentName) {
5747
5794
  const declared = summarizeDeclaredPermissions(metadata);
@@ -5781,39 +5828,39 @@ var SpecialistTransformer = class {
5781
5828
  }
5782
5829
  case "cursor-mdc": return {
5783
5830
  name: `${fullName}.mdc`,
5784
- content: `${unenforceableWarningComment(metadata, "Cursor")}---\n${dumpFrontmatter({
5831
+ content: `---\n${dumpFrontmatter({
5785
5832
  description,
5786
5833
  globs: ["**/*"]
5787
- })}\n---\n# Specialist: ${baseName}\n\n${body}`
5834
+ })}\n---\n${unenforceableWarningComment(metadata, "Cursor")}\n# Specialist: ${baseName}\n\n${body}`
5788
5835
  };
5789
5836
  case "copilot-instructions": return {
5790
5837
  name: `${fullName}.instructions.md`,
5791
- content: `${unenforceableWarningComment(metadata, "Copilot")}---\n${dumpFrontmatter({
5838
+ content: `---\n${dumpFrontmatter({
5792
5839
  description,
5793
5840
  applyTo: "**/*"
5794
- })}\n---\n\n${body}`
5841
+ })}\n---\n${unenforceableWarningComment(metadata, "Copilot")}\n${body}`
5795
5842
  };
5796
5843
  case "opencode-md": return {
5797
5844
  name: `${baseName}.md`,
5798
- content: `${unenforceableWarningComment(metadata, "OpenCode")}---\n${dumpFrontmatter({
5845
+ content: `---\n${dumpFrontmatter({
5799
5846
  description,
5800
5847
  mode: "subagent"
5801
- })}\n---\n\n${body}`
5848
+ })}\n---\n${unenforceableWarningComment(metadata, "OpenCode")}\n${body}`
5802
5849
  };
5803
5850
  case "gemini-md": return {
5804
5851
  name: `${baseName}.md`,
5805
- content: `${unenforceableWarningComment(metadata, "Gemini")}---\n${dumpFrontmatter({
5852
+ content: `---\n${dumpFrontmatter({
5806
5853
  name: baseName,
5807
5854
  description,
5808
5855
  kind: "local"
5809
- })}\n---\n\n${body}`
5856
+ })}\n---\n${unenforceableWarningComment(metadata, "Gemini")}\n${body}`
5810
5857
  };
5811
5858
  case "kiro-md": return {
5812
5859
  name: `${baseName}.md`,
5813
- content: `${unenforceableWarningComment(metadata, "Kiro")}---\n${dumpFrontmatter({
5860
+ content: `---\n${dumpFrontmatter({
5814
5861
  name: baseName,
5815
5862
  description
5816
- })}\n---\n\n${body}`
5863
+ })}\n---\n${unenforceableWarningComment(metadata, "Kiro")}\n${body}`
5817
5864
  };
5818
5865
  case "codex-toml": {
5819
5866
  const sandboxMode = sandboxModeFor(metadata.risk_tier);
@@ -5875,7 +5922,7 @@ var SpecialistSyncService = class {
5875
5922
  }
5876
5923
  return specialists;
5877
5924
  }
5878
- async syncCollectedSpecialists(rootDir, agents, specialists, writer = new PassthroughWriter()) {
5925
+ async syncCollectedSpecialists(rootDir, agents, specialists, writer = new PassthroughWriter(rootDir)) {
5879
5926
  if (specialists.length === 0) return 0;
5880
5927
  for (const agentId of agents) {
5881
5928
  const agentDef = SUPPORTED_AGENTS.find((a) => a.id === agentId);
@@ -5907,7 +5954,7 @@ var SpecialistSyncService = class {
5907
5954
  * @param agents List of agents to sync for
5908
5955
  * @param sourceDir Optional custom source directory for specialists
5909
5956
  */
5910
- async syncSpecialists(rootDir, agents, sourceDir, writer = new PassthroughWriter()) {
5957
+ async syncSpecialists(rootDir, agents, sourceDir, writer = new PassthroughWriter(rootDir)) {
5911
5958
  const specialistsDir = sourceDir || path.default.join(rootDir, "skills/specialists");
5912
5959
  if (!await fs_extra.default.pathExists(specialistsDir)) return 0;
5913
5960
  const specialistFolders = (await fs_extra.default.readdir(specialistsDir)).filter((f) => {
@@ -6086,8 +6133,10 @@ var BackupSession = class {
6086
6133
  const normalized = rel.replace(/\\/g, "/");
6087
6134
  if (this.added.has(normalized)) return;
6088
6135
  const src = path.default.join(this.rootDir, normalized);
6136
+ await assertNoSymlinksUnderRoot(this.rootDir, src);
6089
6137
  if (!await fs_extra.default.pathExists(src)) return;
6090
6138
  const dest = path.default.join(this.backupDir, "files", normalized);
6139
+ await assertNoSymlinksUnderRoot(this.rootDir, dest);
6091
6140
  await fs_extra.default.copy(src, dest, { overwrite: true });
6092
6141
  this.added.add(normalized);
6093
6142
  }
@@ -6098,7 +6147,9 @@ var BackupSession = class {
6098
6147
  reason: this.reason,
6099
6148
  files: Array.from(this.added)
6100
6149
  };
6101
- 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 });
6102
6153
  const backupsDir = path.default.dirname(this.backupDir);
6103
6154
  if (await fs_extra.default.pathExists(backupsDir)) {
6104
6155
  const dirs = (await fs_extra.default.readdir(backupsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -6134,12 +6185,15 @@ var BackupService = class {
6134
6185
  }
6135
6186
  async begin(rootDir, reason) {
6136
6187
  const agsDir = path.default.join(rootDir, ".ags");
6188
+ await assertNoSymlinksUnderRoot(rootDir, agsDir);
6137
6189
  await fs_extra.default.ensureDir(agsDir);
6138
6190
  const gitignorePath = path.default.join(agsDir, ".gitignore");
6191
+ await assertNoSymlinksUnderRoot(rootDir, gitignorePath);
6139
6192
  const defaultGitignore = "backups/\npolicy-candidates.json\n";
6140
6193
  if (!await fs_extra.default.pathExists(gitignorePath)) await fs_extra.default.writeFile(gitignorePath, defaultGitignore, "utf8");
6141
6194
  else if (await fs_extra.default.readFile(gitignorePath, "utf8") === "*\n") await fs_extra.default.writeFile(gitignorePath, defaultGitignore, "utf8");
6142
6195
  const backupsDir = path.default.join(agsDir, "backups");
6196
+ await assertNoSymlinksUnderRoot(rootDir, backupsDir);
6143
6197
  await fs_extra.default.ensureDir(backupsDir);
6144
6198
  const date = this.now();
6145
6199
  const baseId = this.formatId(date);
@@ -6150,6 +6204,7 @@ var BackupService = class {
6150
6204
  counter++;
6151
6205
  }
6152
6206
  const backupDir = path.default.join(backupsDir, id);
6207
+ await assertNoSymlinksUnderRoot(rootDir, backupDir);
6153
6208
  await fs_extra.default.ensureDir(backupDir);
6154
6209
  return new BackupSession(id, rootDir, backupDir, reason, date.toISOString(), this.keep);
6155
6210
  }
@@ -6179,6 +6234,7 @@ var BackupService = class {
6179
6234
  const src = path.default.join(backupDir, "files", rel);
6180
6235
  if (await fs_extra.default.pathExists(src)) {
6181
6236
  const dest = path.default.join(rootDir, rel);
6237
+ await assertNoSymlinksUnderRoot(rootDir, dest);
6182
6238
  await fs_extra.default.copy(src, dest, { overwrite: true });
6183
6239
  restored.push(rel);
6184
6240
  }
@@ -6323,7 +6379,7 @@ var SyncService = class {
6323
6379
  specialistSyncService = new SpecialistSyncService();
6324
6380
  gitService = new GitService();
6325
6381
  lockfileService = new LockfileService();
6326
- writer = new PassthroughWriter();
6382
+ writer = new PassthroughWriter(process.cwd());
6327
6383
  ownership = null;
6328
6384
  dryRun = false;
6329
6385
  cachedRegistryMetadata = void 0;