@pratikpsl/agent-skills 0.4.0 → 0.6.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/README.md +93 -47
- package/dist/bin/agent-skills.d.ts +3 -0
- package/dist/bin/agent-skills.d.ts.map +1 -0
- package/dist/bin/agent-skills.js +6 -0
- package/dist/bin/agent-skills.js.map +1 -0
- package/dist/cli/commands/add.d.ts +3 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/add.js +71 -0
- package/dist/cli/commands/add.js.map +1 -0
- package/dist/cli/commands/dotnet-setup.d.ts +14 -0
- package/dist/cli/commands/dotnet-setup.d.ts.map +1 -0
- package/dist/cli/commands/dotnet-setup.js +110 -0
- package/dist/cli/commands/dotnet-setup.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +36 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +37 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +29 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/copy-skills.d.ts +28 -0
- package/dist/core/copy-skills.d.ts.map +1 -0
- package/dist/core/copy-skills.js +60 -0
- package/dist/core/copy-skills.js.map +1 -0
- package/dist/core/load-pack.d.ts +15 -0
- package/dist/core/load-pack.d.ts.map +1 -0
- package/dist/core/load-pack.js +35 -0
- package/dist/core/load-pack.js.map +1 -0
- package/dist/core/manifest.d.ts +29 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +75 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/resolve-pack.d.ts +7 -0
- package/dist/core/resolve-pack.d.ts.map +1 -0
- package/dist/core/resolve-pack.js +38 -0
- package/dist/core/resolve-pack.js.map +1 -0
- package/package.json +26 -7
- package/templates/dotnet/AgentSkills/entry-points/antigravity-instructions.md +50 -0
- package/{src/templates → templates}/dotnet/manifest.json +8 -2
- package/bin/agent-skills.js +0 -7
- package/src/__tests__/copySkills.test.js +0 -161
- package/src/__tests__/list.test.js +0 -49
- package/src/__tests__/manifest.test.js +0 -72
- package/src/commands/add.js +0 -89
- package/src/commands/dotnet-setup.js +0 -67
- package/src/commands/init.js +0 -42
- package/src/commands/list.js +0 -41
- package/src/index.js +0 -33
- package/src/lib/copySkills.js +0 -84
- package/src/lib/manifest.js +0 -117
- package/src/lib/resolvePack.js +0 -44
- /package/{src/templates → templates}/dotnet/AgentSkills/OPERATING.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/README.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/agents/README.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/agents/architect.agent.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/agents/developer.agent.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/entry-points/AGENTS.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/entry-points/CLAUDE.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/entry-points/copilot-instructions.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/entry-points/cursor-instructions.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/index.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/api.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/auth.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/csharp.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/db.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/design.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/infra.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/mcp.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/lessons/testing.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/memory/schema.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/INDEX.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/README.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/code-standards/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/core/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/csharp-xunit/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/design/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/dotnet-api/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/dotnet-best-practices/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/harness/SKILL.md +0 -0
- /package/{src/templates → templates}/dotnet/AgentSkills/skills/mcp_dotnet/SKILL.md +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/core/copy-skills.ts
|
|
2
|
+
// Copies skill files from a pack into the target project's AgentSkills/ folder.
|
|
3
|
+
//
|
|
4
|
+
// Rules enforced here:
|
|
5
|
+
// - Never overwrite an existing file without --force; warn and skip instead.
|
|
6
|
+
// - Directories are created recursively as needed.
|
|
7
|
+
// - Returns a summary object for the caller to print: { written, skipped }.
|
|
8
|
+
//
|
|
9
|
+
// This module contains no pack-specific logic. The manifest tells it what
|
|
10
|
+
// to copy; it does not inspect SKILL.md contents.
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, rmSync } from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
/**
|
|
14
|
+
* Copies a list of skill source directories into the target project.
|
|
15
|
+
*/
|
|
16
|
+
export function copySkills(options) {
|
|
17
|
+
const { skills, packDir, targetDir, force } = options;
|
|
18
|
+
const written = [];
|
|
19
|
+
const skipped = [];
|
|
20
|
+
for (const skill of skills) {
|
|
21
|
+
const src = path.join(packDir, skill.path);
|
|
22
|
+
// dest overrides where the file lands in the target project.
|
|
23
|
+
// Used for IDE entry-points (e.g. CLAUDE.md, .github/copilot-instructions.md)
|
|
24
|
+
// that must live outside AgentSkills/ at a specific root-relative path.
|
|
25
|
+
const destRelPath = skill.dest ?? skill.path;
|
|
26
|
+
const dest = path.join(targetDir, destRelPath);
|
|
27
|
+
if (!existsSync(src)) {
|
|
28
|
+
console.warn(` ⚠ Source path for skill "${skill.name}" not found: ${src}`);
|
|
29
|
+
skipped.push(destRelPath);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (existsSync(dest) && !force) {
|
|
33
|
+
console.warn(` ⚠ Skipping "${skill.name}" — destination already exists. Use --force to overwrite.`);
|
|
34
|
+
skipped.push(destRelPath);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
38
|
+
// Remove the destination first when forcing — cpSync throws "src and dest
|
|
39
|
+
// cannot be the same" on Windows if the dest directory already exists,
|
|
40
|
+
// even with force:true.
|
|
41
|
+
if (force && existsSync(dest)) {
|
|
42
|
+
rmSync(dest, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
cpSync(src, dest, { recursive: true });
|
|
45
|
+
written.push(destRelPath);
|
|
46
|
+
console.log(` ✓ ${destRelPath}`);
|
|
47
|
+
}
|
|
48
|
+
return { written, skipped };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Ensures the AgentSkills root directory exists in the target project.
|
|
52
|
+
*
|
|
53
|
+
* @returns Absolute path to AgentSkills/.
|
|
54
|
+
*/
|
|
55
|
+
export function ensureAgentSkillsDir(targetDir) {
|
|
56
|
+
const dir = path.join(targetDir, "AgentSkills");
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=copy-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-skills.js","sourceRoot":"","sources":["../../src/core/copy-skills.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,gFAAgF;AAChF,EAAE;AACF,uBAAuB;AACvB,+EAA+E;AAC/E,qDAAqD;AACrD,8EAA8E;AAC9E,EAAE;AACF,0EAA0E;AAC1E,kDAAkD;AAElD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,IAAI,MAAM,MAAM,CAAC;AAqBxB;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACtD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,6DAA6D;QAC7D,8EAA8E;QAC9E,wEAAwE;QACxE,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE/C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CACV,8BAA8B,KAAK,CAAC,IAAI,gBAAgB,GAAG,EAAE,CAC9D,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QAED,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CACV,iBAAiB,KAAK,CAAC,IAAI,2DAA2D,CACvF,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,0EAA0E;QAC1E,uEAAuE;QACvE,wBAAwB;QACxB,IAAI,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,OAAO,WAAW,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAChD,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Manifest } from "./manifest.js";
|
|
2
|
+
export interface LoadedPack {
|
|
3
|
+
/** Absolute path to the pack root. */
|
|
4
|
+
packDir: string;
|
|
5
|
+
/** Validated manifest for the pack. */
|
|
6
|
+
manifest: Manifest;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a pack by name and reads its manifest.
|
|
10
|
+
* Throws with a user-friendly message on any failure.
|
|
11
|
+
*
|
|
12
|
+
* @throws When the pack cannot be resolved or its manifest is invalid.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadPack(packName: string): LoadedPack;
|
|
15
|
+
//# sourceMappingURL=load-pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-pack.d.ts","sourceRoot":"","sources":["../../src/core/load-pack.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAoBrD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/core/load-pack.ts
|
|
2
|
+
// Shared helper that resolves a pack and reads its manifest in one step.
|
|
3
|
+
//
|
|
4
|
+
// Eliminates the duplicated try/catch pattern that previously appeared
|
|
5
|
+
// in both add.ts and dotnet-setup.ts. Commands that need a pack and its
|
|
6
|
+
// manifest should call this instead of calling resolvePack + readManifest
|
|
7
|
+
// separately.
|
|
8
|
+
import { resolvePack } from "./resolve-pack.js";
|
|
9
|
+
import { readManifest } from "./manifest.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a pack by name and reads its manifest.
|
|
12
|
+
* Throws with a user-friendly message on any failure.
|
|
13
|
+
*
|
|
14
|
+
* @throws When the pack cannot be resolved or its manifest is invalid.
|
|
15
|
+
*/
|
|
16
|
+
export function loadPack(packName) {
|
|
17
|
+
let packDir;
|
|
18
|
+
try {
|
|
19
|
+
packDir = resolvePack(packName);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
throw new Error(`Could not resolve pack "${packName}": ${message}`);
|
|
24
|
+
}
|
|
25
|
+
let manifest;
|
|
26
|
+
try {
|
|
27
|
+
manifest = readManifest(packDir);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
throw new Error(`Could not read manifest for pack "${packName}": ${message}`);
|
|
32
|
+
}
|
|
33
|
+
return { packDir, manifest };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=load-pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-pack.js","sourceRoot":"","sources":["../../src/core/load-pack.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,yEAAyE;AACzE,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,0EAA0E;AAC1E,cAAc;AAEd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAU7C;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,MAAM,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CACb,qCAAqC,QAAQ,MAAM,OAAO,EAAE,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SkillEntry {
|
|
2
|
+
/** Short identifier used as a CLI argument. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Human-readable purpose of the skill. */
|
|
5
|
+
description: string;
|
|
6
|
+
/** Source path of the skill, relative to the pack root. */
|
|
7
|
+
path: string;
|
|
8
|
+
/**
|
|
9
|
+
* Destination path in the target project, relative to the project root.
|
|
10
|
+
* When absent, `path` is used as the destination.
|
|
11
|
+
* Use this for IDE entry-point files that must land outside AgentSkills/.
|
|
12
|
+
*/
|
|
13
|
+
dest?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface Manifest {
|
|
16
|
+
/** Package name. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Semver string. */
|
|
19
|
+
version: string;
|
|
20
|
+
/** All skills provided by this pack. */
|
|
21
|
+
skills: SkillEntry[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads and validates the manifest.json for a pack.
|
|
25
|
+
*
|
|
26
|
+
* @throws When manifest is missing, unreadable, or structurally invalid.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readManifest(packDir: string): Manifest;
|
|
29
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/core/manifest.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAkCtD"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/core/manifest.ts
|
|
2
|
+
// Reads and validates a pack's manifest.json.
|
|
3
|
+
//
|
|
4
|
+
// The manifest is the contract between a skills pack and the CLI.
|
|
5
|
+
// It declares what skills exist, where their source files are, and where
|
|
6
|
+
// they should be placed in the target project.
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
/**
|
|
10
|
+
* Reads and validates the manifest.json for a pack.
|
|
11
|
+
*
|
|
12
|
+
* @throws When manifest is missing, unreadable, or structurally invalid.
|
|
13
|
+
*/
|
|
14
|
+
export function readManifest(packDir) {
|
|
15
|
+
const manifestPath = path.join(packDir, "manifest.json");
|
|
16
|
+
if (!existsSync(manifestPath)) {
|
|
17
|
+
throw new Error(`manifest.json not found in pack at "${packDir}". ` +
|
|
18
|
+
`This pack may be malformed or out of date.`);
|
|
19
|
+
}
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = readFileSync(manifestPath, "utf8");
|
|
23
|
+
// Strip UTF-8 BOM if present.
|
|
24
|
+
if (raw.charCodeAt(0) === 0xfeff) {
|
|
25
|
+
raw = raw.slice(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30
|
+
throw new Error(`Could not read manifest.json: ${message}`);
|
|
31
|
+
}
|
|
32
|
+
let manifest;
|
|
33
|
+
try {
|
|
34
|
+
manifest = JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
throw new Error(`manifest.json at "${manifestPath}" is not valid JSON: ${message}`);
|
|
39
|
+
}
|
|
40
|
+
validateManifest(manifest, manifestPath);
|
|
41
|
+
return manifest;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Throws with a descriptive message if the manifest does not meet the
|
|
45
|
+
* required shape.
|
|
46
|
+
*/
|
|
47
|
+
function validateManifest(manifest, filePath) {
|
|
48
|
+
if (typeof manifest !== "object" || manifest === null) {
|
|
49
|
+
throw new Error(`manifest.json at "${filePath}" must be a JSON object.`);
|
|
50
|
+
}
|
|
51
|
+
const obj = manifest;
|
|
52
|
+
const required = ["name", "version", "skills"];
|
|
53
|
+
for (const key of required) {
|
|
54
|
+
if (!(key in obj)) {
|
|
55
|
+
throw new Error(`manifest.json is missing required field "${key}".`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!Array.isArray(obj.skills)) {
|
|
59
|
+
throw new Error(`manifest.json "skills" must be an array.`);
|
|
60
|
+
}
|
|
61
|
+
for (const [i, skill] of obj.skills.entries()) {
|
|
62
|
+
const entry = skill;
|
|
63
|
+
for (const field of ["name", "description", "path"]) {
|
|
64
|
+
if (typeof entry[field] !== "string" || !entry[field].trim()) {
|
|
65
|
+
throw new Error(`manifest.json skills[${i}] is missing or has an empty "${field}" field.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// dest is optional; when present it must be a non-empty string.
|
|
69
|
+
if ("dest" in entry &&
|
|
70
|
+
(typeof entry.dest !== "string" || !entry.dest.trim())) {
|
|
71
|
+
throw new Error(`manifest.json skills[${i}] has an invalid "dest" field — must be a non-empty string when present.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/core/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-pack.d.ts","sourceRoot":"","sources":["../../src/core/resolve-pack.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6BpD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/core/resolve-pack.ts
|
|
2
|
+
// Locates a templates folder bundled inside the package itself.
|
|
3
|
+
// Since everything is in one package, no runtime npm fetching is needed.
|
|
4
|
+
//
|
|
5
|
+
// Templates live at the repo/package root under `templates/<packName>/`.
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
/** Canonical directory names for built-in packs. */
|
|
12
|
+
const PACK_ALIASES = {
|
|
13
|
+
dotnet: "dotnet",
|
|
14
|
+
"skills-dotnet": "dotnet",
|
|
15
|
+
"@pratikpsl/agent-skills-dotnet": "dotnet",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolves a pack name or alias to its installed directory.
|
|
19
|
+
*
|
|
20
|
+
* @throws When the pack cannot be located.
|
|
21
|
+
*/
|
|
22
|
+
export function resolvePack(packName) {
|
|
23
|
+
const normalized = PACK_ALIASES[packName] ??
|
|
24
|
+
packName
|
|
25
|
+
.replace(/^@pratikpsl\/agent-skills-/, "")
|
|
26
|
+
.replace(/^skills-/, "");
|
|
27
|
+
// templates/ lives three levels up from dist/core/ (at the package root)
|
|
28
|
+
// In development (src/core/), it's two levels up — but we always run from
|
|
29
|
+
// the compiled dist/, so three levels is correct.
|
|
30
|
+
const templateDir = path.resolve(__dirname, "..", "..", "templates", normalized);
|
|
31
|
+
if (existsSync(templateDir) &&
|
|
32
|
+
existsSync(path.join(templateDir, "manifest.json"))) {
|
|
33
|
+
return templateDir;
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`Pack "${packName}" (mapped to "${normalized}") is not bundled with this version of the CLI. ` +
|
|
36
|
+
`Currently available packs: dotnet`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=resolve-pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-pack.js","sourceRoot":"","sources":["../../src/core/resolve-pack.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,gEAAgE;AAChE,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,oDAAoD;AACpD,MAAM,YAAY,GAA2B;IAC3C,MAAM,EAAE,QAAQ;IAChB,eAAe,EAAE,QAAQ;IACzB,gCAAgC,EAAE,QAAQ;CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,UAAU,GACd,YAAY,CAAC,QAAQ,CAAC;QACtB,QAAQ;aACL,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC;aACzC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAE7B,yEAAyE;IACzE,0EAA0E;IAC1E,kDAAkD;IAClD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAC9B,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,WAAW,EACX,UAAU,CACX,CAAC;IAEF,IACE,UAAU,CAAC,WAAW,CAAC;QACvB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC,EACnD,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,SAAS,QAAQ,iBAAiB,UAAU,kDAAkD;QAC5F,mCAAmC,CACtC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,37 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pratikpsl/agent-skills",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "CLI to scaffold and manage AI agent skills across projects",
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"description": "CLI to scaffold and manage AI agent skills across projects — like shadcn/ui, but for Cursor, Claude, Codex, Copilot, and more",
|
|
6
6
|
"bin": {
|
|
7
|
-
"agent-skills": "bin/agent-skills.js"
|
|
7
|
+
"agent-skills": "dist/bin/agent-skills.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"dist",
|
|
11
|
+
"templates",
|
|
12
12
|
"README.md",
|
|
13
13
|
"LICENSE"
|
|
14
14
|
],
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=18"
|
|
17
17
|
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ai",
|
|
20
|
+
"agent-skills",
|
|
21
|
+
"cursor",
|
|
22
|
+
"claude",
|
|
23
|
+
"cli",
|
|
24
|
+
"scaffold",
|
|
25
|
+
"copilot",
|
|
26
|
+
"codex",
|
|
27
|
+
"dotnet",
|
|
28
|
+
"agents"
|
|
29
|
+
],
|
|
30
|
+
"homepage": "https://github.com/pratiksinghlad/coding-skills-ai#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/pratiksinghlad/coding-skills-ai/issues"
|
|
33
|
+
},
|
|
18
34
|
"publishConfig": {
|
|
19
35
|
"access": "public"
|
|
20
36
|
},
|
|
21
|
-
"license": "
|
|
37
|
+
"license": "Apache-2.0",
|
|
22
38
|
"repository": {
|
|
23
39
|
"type": "git",
|
|
24
40
|
"url": "git+https://github.com/pratiksinghlad/coding-skills-ai.git"
|
|
25
41
|
},
|
|
26
42
|
"scripts": {
|
|
43
|
+
"build": "tsc",
|
|
27
44
|
"test": "vitest run",
|
|
28
45
|
"test:watch": "vitest",
|
|
29
|
-
"dry-run": "npm publish --dry-run"
|
|
46
|
+
"dry-run": "npm run build && npm publish --dry-run"
|
|
30
47
|
},
|
|
31
48
|
"dependencies": {
|
|
32
49
|
"commander": "^12.0.0"
|
|
33
50
|
},
|
|
34
51
|
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.15.33",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
35
54
|
"vitest": "^3.0.0"
|
|
36
55
|
}
|
|
37
56
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Antigravity Entry Point
|
|
2
|
+
|
|
3
|
+
This file is a lightweight entry point for the Antigravity IDE (Google DeepMind).
|
|
4
|
+
|
|
5
|
+
`AgentSkills/` is the single source of truth for skills, agents, memory,
|
|
6
|
+
prerequisites, checklists, and project rules used by all coding agents and
|
|
7
|
+
AI-enabled IDEs in this repository.
|
|
8
|
+
|
|
9
|
+
## Start Here
|
|
10
|
+
|
|
11
|
+
1. Read `AgentSkills/OPERATING.md`.
|
|
12
|
+
2. Follow skill routing in `AgentSkills/skills/INDEX.md`.
|
|
13
|
+
3. Load role definitions from `AgentSkills/agents/` only when the task requires them.
|
|
14
|
+
|
|
15
|
+
Do not duplicate canonical instructions in this file. Edit shared guidance in
|
|
16
|
+
`AgentSkills/`.
|
|
17
|
+
|
|
18
|
+
## Mandatory Bootstrap (All Agents — Enforced)
|
|
19
|
+
|
|
20
|
+
Before changing any file or producing a final answer for any **code-changing**
|
|
21
|
+
task in this repository, you MUST complete the following bootstrap sequence
|
|
22
|
+
in order. Do NOT skip this sequence even for tasks that appear simple or quick.
|
|
23
|
+
|
|
24
|
+
1. Read `AgentSkills/OPERATING.md`.
|
|
25
|
+
2. Read `AgentSkills/memory/index.md`; load only the domain lesson file(s)
|
|
26
|
+
relevant to the current task (do not load all files at once).
|
|
27
|
+
3. Read `AgentSkills/skills/INDEX.md` (when it exists).
|
|
28
|
+
4. Always load `AgentSkills/skills/core/SKILL.md`.
|
|
29
|
+
5. Load any task-specific skill from `AgentSkills/skills/` that matches the task.
|
|
30
|
+
6. For .NET / C# implementation tasks, load `AgentSkills/agents/developer.agent.md`.
|
|
31
|
+
7. For design, CQRS structure, SQL schema, or architecture tasks, load
|
|
32
|
+
`AgentSkills/agents/architect.agent.md`.
|
|
33
|
+
8. Inspect the worktree context before editing so existing user changes are
|
|
34
|
+
preserved.
|
|
35
|
+
9. Define the verification path for the task before making changes.
|
|
36
|
+
|
|
37
|
+
## Completion Gate (All Agents — Enforced)
|
|
38
|
+
|
|
39
|
+
Before marking any code-changing task complete:
|
|
40
|
+
|
|
41
|
+
1. Run the pre-submit checklist in `AgentSkills/skills/core/SKILL.md`.
|
|
42
|
+
2. Run the harness validator (unless the task is documentation-only or the
|
|
43
|
+
environment cannot run it — in that case, state the reason explicitly):
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
pwsh ./tools/Harness/validate.ps1
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. Do not claim completion when required checks fail. List failures clearly and
|
|
50
|
+
leave the work in a reviewable state.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pratikpsl/agent-skills-dotnet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI agent skill definitions for .NET/C# projects (CQRS, MediatR, EF Core, xUnit, ASP.NET Core).",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
@@ -78,9 +78,15 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"name": "entry-point:cursor",
|
|
81
|
-
"description": "Lightweight Cursor
|
|
81
|
+
"description": "Lightweight Cursor entry point — routes the agent to AgentSkills/OPERATING.md.",
|
|
82
82
|
"path": "AgentSkills/entry-points/cursor-instructions.md",
|
|
83
83
|
"dest": ".cursor/rules/instructions.md"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "entry-point:antigravity",
|
|
87
|
+
"description": "Lightweight Antigravity (Google DeepMind) entry point — routes the agent to AgentSkills/OPERATING.md.",
|
|
88
|
+
"path": "AgentSkills/entry-points/antigravity-instructions.md",
|
|
89
|
+
"dest": ".agents/GEMINI.md"
|
|
84
90
|
}
|
|
85
91
|
]
|
|
86
92
|
}
|
package/bin/agent-skills.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
// src/__tests__/copySkills.test.js
|
|
2
|
-
// Unit tests for the copySkills utility.
|
|
3
|
-
// Key invariants tested:
|
|
4
|
-
// 1. Files are copied when destination does not exist.
|
|
5
|
-
// 2. Existing files are NOT overwritten without --force.
|
|
6
|
-
// 3. Existing files ARE overwritten with --force.
|
|
7
|
-
// 4. Summary counts (written/skipped) are correct.
|
|
8
|
-
// 5. The optional `dest` field routes a file to a different target path (IDE entry-points).
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
import { mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
import { copySkills } from '../lib/copySkills.js';
|
|
15
|
-
|
|
16
|
-
function makeTempDir() {
|
|
17
|
-
const dir = path.join(os.tmpdir(), `agent-skills-copy-test-${Date.now()}`);
|
|
18
|
-
mkdirSync(dir, { recursive: true });
|
|
19
|
-
return dir;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function createSkillSource(packDir, skillRelPath, content = 'skill content') {
|
|
23
|
-
const fullPath = path.join(packDir, skillRelPath);
|
|
24
|
-
mkdirSync(fullPath, { recursive: true });
|
|
25
|
-
writeFileSync(path.join(fullPath, 'SKILL.md'), content);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('copySkills', () => {
|
|
29
|
-
let packDir;
|
|
30
|
-
let targetDir;
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
const ts = Date.now();
|
|
34
|
-
packDir = path.join(os.tmpdir(), `agent-skills-pack-${ts}`);
|
|
35
|
-
targetDir = path.join(os.tmpdir(), `agent-skills-target-${ts}`);
|
|
36
|
-
mkdirSync(packDir, { recursive: true });
|
|
37
|
-
mkdirSync(targetDir, { recursive: true });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
rmSync(packDir, { recursive: true, force: true });
|
|
42
|
-
rmSync(targetDir, { recursive: true, force: true });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const skills = [
|
|
46
|
-
{ name: 'core', description: 'Core principles', path: 'AgentSkills/skills/core' },
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
it('copies a skill when destination does not exist', () => {
|
|
50
|
-
createSkillSource(packDir, 'AgentSkills/skills/core');
|
|
51
|
-
|
|
52
|
-
const { written, skipped } = copySkills({ skills, packDir, targetDir, force: false });
|
|
53
|
-
|
|
54
|
-
expect(written).toHaveLength(1);
|
|
55
|
-
expect(skipped).toHaveLength(0);
|
|
56
|
-
expect(existsSync(path.join(targetDir, 'AgentSkills/skills/core/SKILL.md'))).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('skips an existing skill without --force', () => {
|
|
60
|
-
createSkillSource(packDir, 'AgentSkills/skills/core', 'original');
|
|
61
|
-
// Pre-create destination with different content
|
|
62
|
-
const destSkillDir = path.join(targetDir, 'AgentSkills/skills/core');
|
|
63
|
-
mkdirSync(destSkillDir, { recursive: true });
|
|
64
|
-
writeFileSync(path.join(destSkillDir, 'SKILL.md'), 'existing content');
|
|
65
|
-
|
|
66
|
-
const { written, skipped } = copySkills({ skills, packDir, targetDir, force: false });
|
|
67
|
-
|
|
68
|
-
expect(written).toHaveLength(0);
|
|
69
|
-
expect(skipped).toHaveLength(1);
|
|
70
|
-
// Existing content should be preserved
|
|
71
|
-
const content = readFileSync(path.join(destSkillDir, 'SKILL.md'), 'utf8');
|
|
72
|
-
expect(content).toBe('existing content');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('overwrites an existing skill with --force', () => {
|
|
76
|
-
createSkillSource(packDir, 'AgentSkills/skills/core', 'new content');
|
|
77
|
-
const destSkillDir = path.join(targetDir, 'AgentSkills/skills/core');
|
|
78
|
-
mkdirSync(destSkillDir, { recursive: true });
|
|
79
|
-
writeFileSync(path.join(destSkillDir, 'SKILL.md'), 'old content');
|
|
80
|
-
|
|
81
|
-
const { written, skipped } = copySkills({ skills, packDir, targetDir, force: true });
|
|
82
|
-
|
|
83
|
-
expect(written).toHaveLength(1);
|
|
84
|
-
expect(skipped).toHaveLength(0);
|
|
85
|
-
const content = readFileSync(path.join(destSkillDir, 'SKILL.md'), 'utf8');
|
|
86
|
-
expect(content).toBe('new content');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('returns correct counts for mixed written/skipped', () => {
|
|
90
|
-
const twoSkills = [
|
|
91
|
-
{ name: 'core', description: 'Core', path: 'AgentSkills/skills/core' },
|
|
92
|
-
{ name: 'design', description: 'Design', path: 'AgentSkills/skills/design' },
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
createSkillSource(packDir, 'AgentSkills/skills/core');
|
|
96
|
-
createSkillSource(packDir, 'AgentSkills/skills/design');
|
|
97
|
-
|
|
98
|
-
// Pre-create only core in the destination
|
|
99
|
-
const destCore = path.join(targetDir, 'AgentSkills/skills/core');
|
|
100
|
-
mkdirSync(destCore, { recursive: true });
|
|
101
|
-
writeFileSync(path.join(destCore, 'SKILL.md'), 'existing');
|
|
102
|
-
|
|
103
|
-
const { written, skipped } = copySkills({ skills: twoSkills, packDir, targetDir, force: false });
|
|
104
|
-
|
|
105
|
-
expect(written).toHaveLength(1);
|
|
106
|
-
expect(skipped).toHaveLength(1);
|
|
107
|
-
expect(written[0]).toBe('AgentSkills/skills/design');
|
|
108
|
-
expect(skipped[0]).toBe('AgentSkills/skills/core');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('copies an entry-point file to the dest path, not the source path', () => {
|
|
112
|
-
// Simulate an IDE entry-point: source lives inside the pack, dest is at project root.
|
|
113
|
-
const entryPointSkills = [
|
|
114
|
-
{
|
|
115
|
-
name: 'entry-point:claude',
|
|
116
|
-
description: 'Claude entry point',
|
|
117
|
-
path: 'AgentSkills/entry-points/CLAUDE.md',
|
|
118
|
-
dest: 'CLAUDE.md',
|
|
119
|
-
},
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
// Create source file (a plain file, not a directory)
|
|
123
|
-
const srcDir = path.join(packDir, 'AgentSkills/entry-points');
|
|
124
|
-
mkdirSync(srcDir, { recursive: true });
|
|
125
|
-
writeFileSync(path.join(srcDir, 'CLAUDE.md'), '# Claude Entry Point');
|
|
126
|
-
|
|
127
|
-
const { written, skipped } = copySkills({ skills: entryPointSkills, packDir, targetDir, force: false });
|
|
128
|
-
|
|
129
|
-
// Should land at target root as CLAUDE.md, not inside AgentSkills/entry-points/
|
|
130
|
-
expect(written).toHaveLength(1);
|
|
131
|
-
expect(written[0]).toBe('CLAUDE.md');
|
|
132
|
-
expect(existsSync(path.join(targetDir, 'CLAUDE.md'))).toBe(true);
|
|
133
|
-
expect(existsSync(path.join(targetDir, 'AgentSkills/entry-points/CLAUDE.md'))).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('skips an existing entry-point file without --force (uses dest path)', () => {
|
|
137
|
-
const entryPointSkills = [
|
|
138
|
-
{
|
|
139
|
-
name: 'entry-point:claude',
|
|
140
|
-
description: 'Claude entry point',
|
|
141
|
-
path: 'AgentSkills/entry-points/CLAUDE.md',
|
|
142
|
-
dest: 'CLAUDE.md',
|
|
143
|
-
},
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
const srcDir = path.join(packDir, 'AgentSkills/entry-points');
|
|
147
|
-
mkdirSync(srcDir, { recursive: true });
|
|
148
|
-
writeFileSync(path.join(srcDir, 'CLAUDE.md'), 'new content');
|
|
149
|
-
|
|
150
|
-
// Pre-create destination
|
|
151
|
-
writeFileSync(path.join(targetDir, 'CLAUDE.md'), 'existing content');
|
|
152
|
-
|
|
153
|
-
const { written, skipped } = copySkills({ skills: entryPointSkills, packDir, targetDir, force: false });
|
|
154
|
-
|
|
155
|
-
expect(written).toHaveLength(0);
|
|
156
|
-
expect(skipped).toHaveLength(1);
|
|
157
|
-
expect(skipped[0]).toBe('CLAUDE.md');
|
|
158
|
-
// Existing content untouched
|
|
159
|
-
expect(readFileSync(path.join(targetDir, 'CLAUDE.md'), 'utf8')).toBe('existing content');
|
|
160
|
-
});
|
|
161
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// src/__tests__/list.test.js
|
|
2
|
-
// Integration-style test: verifies that list output matches manifest contents.
|
|
3
|
-
// We don't spawn the CLI process — instead we call the underlying lib functions
|
|
4
|
-
// that the list command uses, which is the behaviour that matters.
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import os from 'os';
|
|
10
|
-
import { readManifest } from '../lib/manifest.js';
|
|
11
|
-
|
|
12
|
-
function makeTempDir() {
|
|
13
|
-
const dir = path.join(os.tmpdir(), `agent-skills-list-test-${Date.now()}`);
|
|
14
|
-
mkdirSync(dir, { recursive: true });
|
|
15
|
-
return dir;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('list — manifest content matches output data', () => {
|
|
19
|
-
let tmpDir;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
tmpDir = makeTempDir();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('returns all skills declared in manifest', () => {
|
|
30
|
-
const manifest = {
|
|
31
|
-
name: '@pratikpsl/agent-skills-dotnet',
|
|
32
|
-
version: '0.1.0',
|
|
33
|
-
skills: [
|
|
34
|
-
{ name: 'core', description: 'Core principles', path: 'AgentSkills/skills/core' },
|
|
35
|
-
{ name: 'design', description: 'Design principles', path: 'AgentSkills/skills/design' },
|
|
36
|
-
{ name: 'dotnet-api', description: '.NET API rules', path: 'AgentSkills/skills/dotnet-api' },
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
writeFileSync(path.join(tmpDir, 'manifest.json'), JSON.stringify(manifest));
|
|
40
|
-
|
|
41
|
-
const result = readManifest(tmpDir);
|
|
42
|
-
|
|
43
|
-
// The list command renders result.skills — verify the data is identical.
|
|
44
|
-
expect(result.skills.map((s) => s.name)).toEqual(['core', 'design', 'dotnet-api']);
|
|
45
|
-
expect(result.skills.every((s) => typeof s.description === 'string')).toBe(true);
|
|
46
|
-
expect(result.name).toBe(manifest.name);
|
|
47
|
-
expect(result.version).toBe(manifest.version);
|
|
48
|
-
});
|
|
49
|
-
});
|