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 +87 -31
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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.
|
|
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.
|
|
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.
|
|
106
|
-
"vite": "^8.3.
|
|
107
|
-
"vitest": "^5.0.
|
|
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.
|
|
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:**
|
|
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,
|
|
5742
|
-
* the declaration
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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;
|