@vegastack/skills 0.1.0 → 0.3.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 +27 -7
- package/dist/index.js +195 -99
- package/package.json +16 -5
- package/skill/arch-guardian/SKILL.md +84 -0
- package/skill/arch-guardian/agents/openai.yaml +4 -0
- package/skill/arch-guardian/assets/adr-template.md +25 -0
- package/skill/arch-guardian/assets/answers-example.json +10 -0
- package/skill/arch-guardian/assets/architecture-profile.json +13 -0
- package/skill/arch-guardian/assets/architecture-profile.schema.json +31 -0
- package/skill/arch-guardian/references/advisory-report.md +65 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-cost.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-data-boundaries.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-evals.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/connectors-sandbox.md +2 -2
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/delivery-operations.md +5 -5
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/durable-execution.md +3 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/flutter.md +1 -1
- package/skill/arch-guardian/references/architecture/foundation.md +31 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/hosting-reliability.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/identity-tenancy.md +7 -7
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/model-lifecycle.md +5 -3
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/models-observability.md +1 -1
- package/skill/arch-guardian/references/architecture/security-privacy.md +27 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/topology-monorepo.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/web.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/foundation-compatibility.json +1 -1
- package/skill/arch-guardian/references/profile-governance.md +40 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/rule-model.json +2 -2
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/workflows.md +15 -12
- package/skill/{vegastack-arch-guardian → arch-guardian}/refresh/REFRESH.md +10 -3
- package/skill/{vegastack-arch-guardian → arch-guardian}/refresh/sources.json +34 -22
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/lib.mjs +1 -10
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/profile-tool.mjs +43 -49
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/refresh-evidence.mjs +46 -5
- package/skill/arch-guardian/scripts/validate-profile.mjs +65 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/verify-corpus.mjs +1 -13
- package/skill/skill-maintainer/SKILL.md +79 -0
- package/skill/skill-maintainer/agents/openai.yaml +4 -0
- package/skill/skill-maintainer/references/release-ops.md +63 -0
- package/skill/skill-maintainer/references/standards.md +92 -0
- package/skill/skill-maintainer/refresh/REFRESH.md +40 -0
- package/skill/skill-maintainer/refresh/sources.json +103 -0
- package/skill/skillify/SKILL.md +154 -0
- package/skill/skillify/agents/openai.yaml +4 -0
- package/skill/skillify/assets/templates/README.md.template +26 -0
- package/skill/skillify/assets/templates/REFRESH.md.template +21 -0
- package/skill/skillify/assets/templates/SKILL.md.template +26 -0
- package/skill/skillify/assets/templates/openai.yaml.template +4 -0
- package/skill/skillify/assets/templates/skill.test.ts.template +40 -0
- package/skill/skillify/assets/templates/sources.json.template +6 -0
- package/skill/skillify/references/authoring.md +72 -0
- package/skill/skillify/references/eval-playbook.md +62 -0
- package/skill/skillify/refresh/REFRESH.md +24 -0
- package/skill/skillify/refresh/sources.json +6 -0
- package/skill/skillify/scripts/scaffold-skill.mjs +138 -0
- package/skill-integrity.json +74 -45
- package/skill/vegastack-arch-guardian/SKILL.md +0 -96
- package/skill/vegastack-arch-guardian/agents/openai.yaml +0 -4
- package/skill/vegastack-arch-guardian/assets/adr-template.md +0 -40
- package/skill/vegastack-arch-guardian/assets/answers-example.json +0 -20
- package/skill/vegastack-arch-guardian/assets/architecture-profile.json +0 -25
- package/skill/vegastack-arch-guardian/assets/architecture-profile.schema.json +0 -213
- package/skill/vegastack-arch-guardian/references/architecture/foundation.md +0 -33
- package/skill/vegastack-arch-guardian/references/architecture/security-privacy.md +0 -23
- package/skill/vegastack-arch-guardian/references/control-catalog.json +0 -55
- package/skill/vegastack-arch-guardian/references/profile-governance.md +0 -54
- package/skill/vegastack-arch-guardian/scripts/architecture-check.mjs +0 -323
- package/skill/vegastack-arch-guardian/scripts/validate-profile.mjs +0 -241
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/deployment-review-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/service-design-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/threat-model-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/agent-product.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/data-memory.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/realtime-channels.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/golden-patterns.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/schema-validate.mjs +0 -0
package/README.md
CHANGED
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
# @vegastack/skills
|
|
2
2
|
|
|
3
|
-
Installer for VegaStack Agent Skills
|
|
3
|
+
Installer for VegaStack Agent Skills — a family of self-contained skills for Claude Code, Codex, and Hermes, shipped in one integrity-checked package.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx @vegastack/skills
|
|
6
|
+
npx @vegastack/skills list
|
|
7
|
+
npx @vegastack/skills add arch-guardian
|
|
7
8
|
```
|
|
8
9
|
|
|
10
|
+
## Skills in this package
|
|
11
|
+
|
|
12
|
+
| Skill | What it does |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `arch-guardian` | Architecture advisor: tier-scoped normative rules activated by declared capabilities, evidence-backed advisory reports, slim committed profile, source-freshness contract |
|
|
15
|
+
| `skill-maintainer` | Encodes the Agent Skills standards (Claude Code, Codex, Hermes, agentskills.io) for creating, updating, and releasing skills in a skills repo |
|
|
16
|
+
| `skillify` | Turns a feature or workflow into a complete skill conforming to the VegaStack skills contract, or audits an existing one |
|
|
17
|
+
|
|
9
18
|
## Commands
|
|
10
19
|
|
|
11
20
|
| Command | What it does |
|
|
12
21
|
|---|---|
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
22
|
+
| `list` | Show the bundled skills |
|
|
23
|
+
| `add <skill>` | Install (or upgrade) a skill into the selected agent directories |
|
|
24
|
+
| `verify [skill]` | Check installed copies against the bundled checksum manifest (all bundled skills when no name given) |
|
|
25
|
+
| `doctor` | Diagnose an install: integrity across all skills, profile presence, installed-vs-latest version |
|
|
26
|
+
| `remove <skill>` | Uninstall a skill from the selected agent directories |
|
|
17
27
|
|
|
18
28
|
## Flags
|
|
19
29
|
|
|
20
30
|
| Flag | Meaning |
|
|
21
31
|
|---|---|
|
|
22
32
|
| `--project` / `--global` | Install into the current project (default) or the user's home directory |
|
|
23
|
-
| `--agent codex\|claude\|both` | Target agent runtime(s) |
|
|
33
|
+
| `--agent codex\|claude\|hermes\|both\|all` | Target agent runtime(s); `both` = codex+claude |
|
|
24
34
|
| `--dir PATH` | Operate on a different project directory |
|
|
25
35
|
| `--dry-run` | Show what would change without writing |
|
|
26
36
|
| `--force` | Overwrite a modified installed copy |
|
|
27
37
|
| `--non-interactive` | Skip prompts and use defaults: `--agent both`, project-local (for automation) |
|
|
28
38
|
|
|
39
|
+
## Agent surfaces
|
|
40
|
+
|
|
41
|
+
| Agent | Project install | Global install |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Claude Code | `.claude/skills/` | `~/.claude/skills/` |
|
|
44
|
+
| Codex | `.agents/skills/` | `~/.agents/skills/` |
|
|
45
|
+
| Hermes | — (Hermes discovers skills globally only) | `~/.hermes/skills/` |
|
|
46
|
+
|
|
47
|
+
`--agent hermes` therefore requires `--global`; `--agent all` on a project install covers codex+claude and prints a notice about hermes.
|
|
48
|
+
|
|
29
49
|
## Integrity model
|
|
30
50
|
|
|
31
51
|
The package ships a checksum manifest that is verified at install and by `verify` — it proves the installed bytes match what was packed, not who published it. Publisher identity is attested separately by npm provenance, generated by the trusted-publishing release pipeline. Verify it with `npm audit signatures` or on the package's npm page.
|
package/dist/index.js
CHANGED
|
@@ -10,25 +10,32 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
12
|
var packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
|
-
var
|
|
14
|
-
var surfaces = { codex: ".agents/skills", claude: ".claude/skills" };
|
|
13
|
+
var bundleRoot = join(packageRoot, "skill");
|
|
14
|
+
var surfaces = { codex: ".agents/skills", claude: ".claude/skills", hermes: ".hermes/skills" };
|
|
15
15
|
var packageVersion = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8")).version;
|
|
16
16
|
function usage() {
|
|
17
|
-
return `Usage: vegastack-skills <add|verify|
|
|
17
|
+
return `Usage: vegastack-skills <add|verify|remove> <skill> [options]
|
|
18
|
+
vegastack-skills <list|doctor> [options]
|
|
18
19
|
|
|
19
20
|
Options:
|
|
20
|
-
--agent codex|claude|both
|
|
21
|
+
--agent codex|claude|hermes|both|all (both = codex+claude; hermes is global-only)
|
|
21
22
|
--project | --global
|
|
22
23
|
--dir PATH
|
|
23
24
|
--dry-run
|
|
24
25
|
--force
|
|
25
26
|
--non-interactive
|
|
26
27
|
--version
|
|
28
|
+
|
|
29
|
+
Run "vegastack-skills list" to see the bundled skills.
|
|
27
30
|
`;
|
|
28
31
|
}
|
|
32
|
+
async function bundledSkills() {
|
|
33
|
+
const entries = await readdir(bundleRoot, { withFileTypes: true });
|
|
34
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
35
|
+
}
|
|
29
36
|
function parse(argv) {
|
|
30
37
|
const command = argv[0] && !argv[0].startsWith("-") ? argv.shift() : "help";
|
|
31
|
-
const options = { command,
|
|
38
|
+
const options = { command, dryRun: false, force: false, nonInteractive: false };
|
|
32
39
|
if (argv[0] && !argv[0].startsWith("-"))
|
|
33
40
|
options.skill = argv.shift();
|
|
34
41
|
while (argv.length) {
|
|
@@ -54,16 +61,22 @@ function parse(argv) {
|
|
|
54
61
|
else
|
|
55
62
|
throw new Error(`Unknown option: ${flag}`);
|
|
56
63
|
}
|
|
57
|
-
if (!["add", "verify", "doctor", "remove", "version", "help"].includes(options.command))
|
|
64
|
+
if (!["add", "verify", "doctor", "remove", "list", "version", "help"].includes(options.command))
|
|
58
65
|
throw new Error(`Unknown command: ${options.command}`);
|
|
59
|
-
if (options.
|
|
60
|
-
throw new Error(`Unknown skill: ${options.skill}`);
|
|
61
|
-
if (options.agent && !["codex", "claude", "both"].includes(options.agent))
|
|
66
|
+
if (options.agent && !["codex", "claude", "hermes", "both", "all"].includes(options.agent))
|
|
62
67
|
throw new Error(`Invalid --agent: ${options.agent}`);
|
|
63
68
|
if (options.mode === "global" && options.dir)
|
|
64
69
|
throw new Error("--dir cannot be combined with --global");
|
|
65
70
|
return options;
|
|
66
71
|
}
|
|
72
|
+
async function requireSkill(options) {
|
|
73
|
+
const skills = await bundledSkills();
|
|
74
|
+
if (!options.skill)
|
|
75
|
+
throw new Error(`Specify a skill: ${skills.join(", ")}`);
|
|
76
|
+
if (!skills.includes(options.skill))
|
|
77
|
+
throw new Error(`Unknown skill: ${options.skill}. Bundled skills: ${skills.join(", ")}`);
|
|
78
|
+
return options.skill;
|
|
79
|
+
}
|
|
67
80
|
async function prompt(options) {
|
|
68
81
|
if (options.agent && options.mode)
|
|
69
82
|
return { agent: options.agent, mode: options.mode };
|
|
@@ -71,15 +84,25 @@ async function prompt(options) {
|
|
|
71
84
|
return { agent: options.agent ?? "both", mode: options.mode ?? "project" };
|
|
72
85
|
}
|
|
73
86
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
74
|
-
const agentAnswer = options.agent ?? await rl.question("Install for codex, claude, or
|
|
87
|
+
const agentAnswer = options.agent ?? await rl.question("Install for codex, claude, hermes, both (codex+claude), or all? [both] ");
|
|
75
88
|
const modeAnswer = options.mode ?? await rl.question("Install project-local or user-global? [project] ");
|
|
76
89
|
rl.close();
|
|
77
90
|
const agent = agentAnswer || "both";
|
|
78
91
|
const mode = modeAnswer === "global" ? "global" : "project";
|
|
79
|
-
if (!["codex", "claude", "both"].includes(agent))
|
|
92
|
+
if (!["codex", "claude", "hermes", "both", "all"].includes(agent))
|
|
80
93
|
throw new Error(`Invalid agent choice: ${agent}`);
|
|
81
94
|
return { agent, mode };
|
|
82
95
|
}
|
|
96
|
+
function resolveAgents(choice, mode) {
|
|
97
|
+
const wanted = choice === "both" ? ["codex", "claude"] : choice === "all" ? ["codex", "claude", "hermes"] : [choice];
|
|
98
|
+
if (mode === "project" && wanted.includes("hermes")) {
|
|
99
|
+
if (choice === "hermes")
|
|
100
|
+
throw new Error("Hermes discovers skills only in the global ~/.hermes/skills directory; use --global (without --dir) with --agent hermes, or --agent all");
|
|
101
|
+
console.log("note: skipping hermes for a project install — Hermes discovers skills globally only; run with --global --agent hermes");
|
|
102
|
+
return wanted.filter((agent) => agent !== "hermes");
|
|
103
|
+
}
|
|
104
|
+
return wanted;
|
|
105
|
+
}
|
|
83
106
|
async function exists(path) {
|
|
84
107
|
try {
|
|
85
108
|
await lstat(path);
|
|
@@ -140,24 +163,29 @@ async function recoverInstall(base) {
|
|
|
140
163
|
return;
|
|
141
164
|
await assertNoSymlink(journalPath, false);
|
|
142
165
|
const journal = JSON.parse(await readFile(journalPath, "utf8"));
|
|
143
|
-
if (journal.schemaVersion !==
|
|
144
|
-
throw new Error(`
|
|
145
|
-
|
|
166
|
+
if (journal.schemaVersion !== 2 || !["prepared", "committed"].includes(journal.status) || !Array.isArray(journal.operations)) {
|
|
167
|
+
throw new Error(`Unsupported installer recovery journal (schemaVersion ${journal.schemaVersion ?? "unknown"}); inspect and remove it manually: ${journalPath}`);
|
|
168
|
+
}
|
|
169
|
+
const skills = new Set(await bundledSkills());
|
|
170
|
+
const seen = new Set;
|
|
146
171
|
for (const operation of journal.operations) {
|
|
147
|
-
if (!["codex", "claude"].includes(operation.agent) ||
|
|
148
|
-
throw new Error(`Untrusted installer recovery journal: invalid agent`);
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
if (!["codex", "claude", "hermes"].includes(operation.agent) || typeof operation.skill !== "string" || !skills.has(operation.skill))
|
|
173
|
+
throw new Error(`Untrusted installer recovery journal: invalid agent or skill; inspect and remove it manually: ${journalPath}`);
|
|
174
|
+
const key = `${operation.agent}/${operation.skill}`;
|
|
175
|
+
if (seen.has(key))
|
|
176
|
+
throw new Error("Untrusted installer recovery journal: duplicate operation");
|
|
177
|
+
seen.add(key);
|
|
178
|
+
const expectedDestination = join(base, surfaces[operation.agent], operation.skill);
|
|
151
179
|
if (resolve(operation.destination) !== expectedDestination || typeof operation.existed !== "boolean")
|
|
152
|
-
throw new Error(
|
|
180
|
+
throw new Error("Untrusted installer recovery journal: destination outside installer roots");
|
|
153
181
|
const expectedParent = dirname(expectedDestination);
|
|
154
182
|
const validateTemporary = (path, kind) => {
|
|
155
183
|
if (!path)
|
|
156
184
|
return kind === "backup" && !operation.existed;
|
|
157
|
-
return dirname(resolve(path)) === expectedParent && basename(path).startsWith(`.${
|
|
185
|
+
return dirname(resolve(path)) === expectedParent && basename(path).startsWith(`.${operation.skill}.${kind}-`);
|
|
158
186
|
};
|
|
159
187
|
if (!validateTemporary(operation.stage, "stage") || !validateTemporary(operation.backup, "backup"))
|
|
160
|
-
throw new Error(
|
|
188
|
+
throw new Error("Untrusted installer recovery journal: invalid transaction path");
|
|
161
189
|
}
|
|
162
190
|
for (const operation of [...journal.operations].reverse()) {
|
|
163
191
|
for (const path of [operation.destination, operation.stage, operation.backup].filter(Boolean))
|
|
@@ -237,76 +265,89 @@ async function listFiles(root) {
|
|
|
237
265
|
return output.sort();
|
|
238
266
|
}
|
|
239
267
|
var hash = (body) => createHash("sha256").update(body).digest("hex");
|
|
240
|
-
async function
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
const manifest = JSON.parse(await readFile(integrityPath, "utf8"));
|
|
244
|
-
if (manifest.schemaVersion !== 1 || manifest.skill !== skillName)
|
|
268
|
+
async function loadManifest() {
|
|
269
|
+
const manifest = JSON.parse(await readFile(join(packageRoot, "skill-integrity.json"), "utf8"));
|
|
270
|
+
if (manifest.schemaVersion !== 2 || typeof manifest.skills !== "object")
|
|
245
271
|
throw new Error("Invalid bundled skill manifest");
|
|
272
|
+
return manifest;
|
|
273
|
+
}
|
|
274
|
+
async function loadSource(skillName) {
|
|
275
|
+
const manifest = await loadManifest();
|
|
276
|
+
const skillManifest = manifest.skills[skillName];
|
|
277
|
+
if (!skillManifest)
|
|
278
|
+
throw new Error(`Bundled manifest has no entry for skill ${skillName}`);
|
|
279
|
+
const source = join(bundleRoot, skillName);
|
|
246
280
|
const skill = await readFile(join(source, "SKILL.md"), "utf8");
|
|
247
281
|
if (!skill.startsWith(`---
|
|
248
|
-
`) ||
|
|
249
|
-
throw new Error(
|
|
282
|
+
`) || !new RegExp(`^name: ${skillName}$`, "m").test(skill) || !/^description: .+/m.test(skill)) {
|
|
283
|
+
throw new Error(`Bundled skill ${skillName} fails Agent Skills frontmatter validation`);
|
|
250
284
|
}
|
|
251
285
|
const observed = {};
|
|
252
286
|
for (const file of await listFiles(source))
|
|
253
287
|
observed[relative(source, file).split(sep).join("/")] = hash(await readFile(file));
|
|
254
|
-
if (JSON.stringify(observed) !== JSON.stringify(
|
|
255
|
-
throw new Error(
|
|
256
|
-
return { source,
|
|
288
|
+
if (JSON.stringify(observed) !== JSON.stringify(skillManifest.files))
|
|
289
|
+
throw new Error(`Bundled skill ${skillName} checksum mismatch`);
|
|
290
|
+
return { source, files: skillManifest.files };
|
|
257
291
|
}
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
const base = mode === "global" ? homedir() : resolve(directory ?? process.cwd());
|
|
261
|
-
return agents.map((item) => ({ agent: item, destination: join(base, surfaces[item], skillName) }));
|
|
292
|
+
function baseFor(mode, directory) {
|
|
293
|
+
return mode === "global" ? homedir() : resolve(directory ?? process.cwd());
|
|
262
294
|
}
|
|
263
|
-
async function compare(destination,
|
|
295
|
+
async function compare(destination, files) {
|
|
264
296
|
if (!await exists(destination))
|
|
265
297
|
return { status: "missing", issues: ["not installed"] };
|
|
266
298
|
await assertNoSymlink(destination, false);
|
|
267
299
|
const issues = [];
|
|
268
300
|
const actualFiles = await listFiles(destination);
|
|
269
301
|
const actualKeys = new Set(actualFiles.map((file) => relative(destination, file).split(sep).join("/")).filter((key) => key !== ".vegastack-install.json"));
|
|
270
|
-
for (const [key, expected] of Object.entries(
|
|
302
|
+
for (const [key, expected] of Object.entries(files)) {
|
|
271
303
|
if (!actualKeys.has(key))
|
|
272
304
|
issues.push(`missing ${key}`);
|
|
273
305
|
else if (hash(await readFile(join(destination, key))) !== expected)
|
|
274
306
|
issues.push(`changed ${key}`);
|
|
275
307
|
}
|
|
276
308
|
for (const key of actualKeys)
|
|
277
|
-
if (!(key in
|
|
309
|
+
if (!(key in files))
|
|
278
310
|
issues.push(`unexpected ${key}`);
|
|
279
311
|
return { status: issues.length ? "drifted" : "verified", issues };
|
|
280
312
|
}
|
|
281
313
|
async function install(options) {
|
|
314
|
+
const skillName = await requireSkill(options);
|
|
282
315
|
const choice = await prompt(options);
|
|
283
|
-
const base = choice.mode
|
|
316
|
+
const base = baseFor(choice.mode, options.dir);
|
|
317
|
+
const agents = resolveAgents(choice.agent, choice.mode);
|
|
318
|
+
if (!agents.length)
|
|
319
|
+
return;
|
|
284
320
|
if (!options.dryRun)
|
|
285
|
-
return withInstallLock(base, () => installLocked(options,
|
|
286
|
-
return installLocked(options,
|
|
321
|
+
return withInstallLock(base, () => installLocked(options, skillName, agents, base));
|
|
322
|
+
return installLocked(options, skillName, agents, base, false);
|
|
287
323
|
}
|
|
288
|
-
async function installLocked(options,
|
|
324
|
+
async function installLocked(options, skillName, agents, base, recover = true) {
|
|
289
325
|
if (recover)
|
|
290
326
|
await recoverInstall(base);
|
|
291
|
-
const { source,
|
|
292
|
-
const targets = destinations(choice.agent, choice.mode, options.dir);
|
|
327
|
+
const { source, files } = await loadSource(skillName);
|
|
293
328
|
const operations = [];
|
|
294
|
-
for (const
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
for (const agent of agents) {
|
|
330
|
+
const destination = join(base, surfaces[agent], skillName);
|
|
331
|
+
await assertNoSymlink(destination);
|
|
332
|
+
const parent = dirname(destination);
|
|
297
333
|
await assertNoSymlink(parent);
|
|
298
|
-
const existed = await exists(
|
|
334
|
+
const existed = await exists(destination);
|
|
299
335
|
if (existed) {
|
|
300
|
-
const comparison = await compare(
|
|
336
|
+
const comparison = await compare(destination, files);
|
|
301
337
|
if (comparison.status === "verified" && !options.force) {
|
|
302
|
-
console.log(`unchanged ${
|
|
338
|
+
console.log(`unchanged ${agent}: ${destination}`);
|
|
303
339
|
continue;
|
|
304
340
|
}
|
|
305
|
-
if (!options.force)
|
|
306
|
-
|
|
341
|
+
if (!options.force) {
|
|
342
|
+
if (options.dryRun) {
|
|
343
|
+
console.log(`would replace ${agent} (requires --force; installed copy differs): ${destination}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
throw new Error(`Refusing differing installation without --force: ${destination}`);
|
|
347
|
+
}
|
|
307
348
|
}
|
|
308
349
|
const suffix = randomUUID();
|
|
309
|
-
operations.push({
|
|
350
|
+
operations.push({ skill: skillName, agent, destination, existed, stage: join(parent, `.${skillName}.stage-${suffix}`), backup: existed ? join(parent, `.${skillName}.backup-${suffix}`) : undefined });
|
|
310
351
|
}
|
|
311
352
|
if (options.dryRun) {
|
|
312
353
|
for (const operation of operations)
|
|
@@ -323,14 +364,14 @@ async function installLocked(options, choice, base, recover = true) {
|
|
|
323
364
|
await mkdir(dirname(operation.destination), { recursive: true });
|
|
324
365
|
await assertNoSymlink(dirname(operation.destination), false);
|
|
325
366
|
await cp(source, operation.stage, { recursive: true, dereference: false, errorOnExist: true });
|
|
326
|
-
await writeFile(join(operation.stage, ".vegastack-install.json"), `${JSON.stringify({ installer: "@vegastack/skills", version: packageVersion,
|
|
367
|
+
await writeFile(join(operation.stage, ".vegastack-install.json"), `${JSON.stringify({ installer: "@vegastack/skills", version: packageVersion, skill: skillName, files }, null, 2)}
|
|
327
368
|
`, { flag: "wx" });
|
|
328
|
-
const stagedCheck = await compare(operation.stage,
|
|
369
|
+
const stagedCheck = await compare(operation.stage, files);
|
|
329
370
|
if (stagedCheck.status !== "verified")
|
|
330
371
|
throw new Error(`Staged copy failed verification: ${stagedCheck.issues.join(", ")}`);
|
|
331
372
|
staged.push(operation);
|
|
332
373
|
}
|
|
333
|
-
await durableJson(journalPath, { schemaVersion:
|
|
374
|
+
await durableJson(journalPath, { schemaVersion: 2, status: "prepared", operations });
|
|
334
375
|
for (const operation of operations) {
|
|
335
376
|
await assertNoSymlink(dirname(operation.destination), false);
|
|
336
377
|
if (await exists(operation.destination))
|
|
@@ -346,7 +387,7 @@ async function installLocked(options, choice, base, recover = true) {
|
|
|
346
387
|
}
|
|
347
388
|
applied.push(operation);
|
|
348
389
|
}
|
|
349
|
-
await durableJson(journalPath, { schemaVersion:
|
|
390
|
+
await durableJson(journalPath, { schemaVersion: 2, status: "committed", operations });
|
|
350
391
|
} catch (error) {
|
|
351
392
|
if (await exists(journalPath))
|
|
352
393
|
await recoverInstall(base);
|
|
@@ -375,17 +416,44 @@ async function installLocked(options, choice, base, recover = true) {
|
|
|
375
416
|
}
|
|
376
417
|
async function verify(options) {
|
|
377
418
|
const choice = await prompt(options);
|
|
378
|
-
const
|
|
419
|
+
const base = baseFor(choice.mode, options.dir);
|
|
420
|
+
const agents = resolveAgents(choice.agent, choice.mode);
|
|
421
|
+
const explicit = Boolean(options.skill);
|
|
422
|
+
const skills = explicit ? [await requireSkill(options)] : await bundledSkills();
|
|
379
423
|
let failed = false;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
424
|
+
let found = 0;
|
|
425
|
+
for (const skillName of skills) {
|
|
426
|
+
const { files } = await loadSource(skillName);
|
|
427
|
+
for (const agent of agents) {
|
|
428
|
+
const destination = join(base, surfaces[agent], skillName);
|
|
429
|
+
const result = await compare(destination, files);
|
|
430
|
+
if (result.status === "missing" && !explicit) {
|
|
431
|
+
console.log(`not installed ${agent} ${skillName}`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
found += 1;
|
|
435
|
+
console.log(`${result.status} ${agent} ${skillName}: ${destination}${result.issues.length ? ` (${result.issues.join(", ")})` : ""}`);
|
|
436
|
+
if (result.status !== "verified")
|
|
437
|
+
failed = true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!explicit && found === 0) {
|
|
441
|
+
console.log("no bundled skills are installed on the selected surfaces");
|
|
442
|
+
failed = true;
|
|
385
443
|
}
|
|
386
444
|
if (failed)
|
|
387
445
|
process.exitCode = 1;
|
|
388
446
|
}
|
|
447
|
+
function semverLess(a, b) {
|
|
448
|
+
const parse2 = (value) => value.split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
449
|
+
const [aMajor = 0, aMinor = 0, aPatch = 0] = parse2(a);
|
|
450
|
+
const [bMajor = 0, bMinor = 0, bPatch = 0] = parse2(b);
|
|
451
|
+
if (aMajor !== bMajor)
|
|
452
|
+
return aMajor < bMajor;
|
|
453
|
+
if (aMinor !== bMinor)
|
|
454
|
+
return aMinor < bMinor;
|
|
455
|
+
return aPatch < bPatch;
|
|
456
|
+
}
|
|
389
457
|
async function latestPublishedVersion() {
|
|
390
458
|
try {
|
|
391
459
|
const response = await fetch("https://registry.npmjs.org/@vegastack%2fskills/latest", { signal: AbortSignal.timeout(3000) });
|
|
@@ -398,37 +466,57 @@ async function latestPublishedVersion() {
|
|
|
398
466
|
}
|
|
399
467
|
}
|
|
400
468
|
async function removeSkill(options) {
|
|
469
|
+
const skillName = await requireSkill(options);
|
|
401
470
|
const choice = await prompt(options);
|
|
471
|
+
const base = baseFor(choice.mode, options.dir);
|
|
472
|
+
const agents = resolveAgents(choice.agent, choice.mode);
|
|
473
|
+
if (!options.dryRun)
|
|
474
|
+
return withInstallLock(base, () => removeLocked(options, skillName, agents, base));
|
|
475
|
+
return removeLocked(options, skillName, agents, base);
|
|
476
|
+
}
|
|
477
|
+
async function removeLocked(options, skillName, agents, base) {
|
|
402
478
|
let removed = 0;
|
|
403
|
-
for (const
|
|
404
|
-
|
|
405
|
-
|
|
479
|
+
for (const agent of agents) {
|
|
480
|
+
const destination = join(base, surfaces[agent], skillName);
|
|
481
|
+
if (!await exists(destination)) {
|
|
482
|
+
console.log(`not installed ${agent}: ${destination}`);
|
|
406
483
|
continue;
|
|
407
484
|
}
|
|
408
|
-
await assertNoSymlink(
|
|
485
|
+
await assertNoSymlink(destination, false);
|
|
409
486
|
if (!options.force) {
|
|
410
|
-
const {
|
|
411
|
-
const comparison = await compare(
|
|
487
|
+
const { files } = await loadSource(skillName);
|
|
488
|
+
const comparison = await compare(destination, files);
|
|
412
489
|
if (comparison.status === "drifted")
|
|
413
|
-
throw new Error(`Installation differs from the bundled skill (possibly locally modified); re-run with --force to remove anyway: ${
|
|
490
|
+
throw new Error(`Installation differs from the bundled skill (possibly locally modified); re-run with --force to remove anyway: ${destination}`);
|
|
414
491
|
}
|
|
415
492
|
if (options.dryRun) {
|
|
416
|
-
console.log(`would remove ${
|
|
493
|
+
console.log(`would remove ${agent}: ${destination}`);
|
|
417
494
|
continue;
|
|
418
495
|
}
|
|
419
|
-
await rm(
|
|
496
|
+
await rm(destination, { recursive: true, force: true });
|
|
420
497
|
removed += 1;
|
|
421
|
-
console.log(`removed ${
|
|
498
|
+
console.log(`removed ${agent}: ${destination}`);
|
|
422
499
|
}
|
|
423
500
|
if (!removed && !options.dryRun)
|
|
424
501
|
process.exitCode = 1;
|
|
425
502
|
}
|
|
503
|
+
async function list() {
|
|
504
|
+
const manifest = await loadManifest();
|
|
505
|
+
for (const skillName of await bundledSkills()) {
|
|
506
|
+
const skill = await readFile(join(bundleRoot, skillName, "SKILL.md"), "utf8");
|
|
507
|
+
const description = skill.match(/^description: (.+)$/m)?.[1] ?? "";
|
|
508
|
+
const fileCount = Object.keys(manifest.skills[skillName]?.files ?? {}).length;
|
|
509
|
+
console.log(`${skillName} (${fileCount} files)`);
|
|
510
|
+
console.log(` ${description.length > 160 ? `${description.slice(0, 157)}...` : description}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
426
513
|
async function doctor(options) {
|
|
427
|
-
const base = options.mode
|
|
514
|
+
const base = baseFor(options.mode ?? "project", options.dir);
|
|
428
515
|
await access(base, fsConstants.R_OK | fsConstants.W_OK);
|
|
429
516
|
await assertNoSymlink(base, false);
|
|
430
517
|
let failed = false;
|
|
431
518
|
const profileCandidates = [join(base, ".vegastack", "architecture.json"), join(base, ".vegastack", "architecture.yaml")];
|
|
519
|
+
let foundProfilePath = null;
|
|
432
520
|
if (options.mode !== "global") {
|
|
433
521
|
let profile = null;
|
|
434
522
|
for (const candidate of profileCandidates)
|
|
@@ -436,56 +524,62 @@ async function doctor(options) {
|
|
|
436
524
|
profile = candidate;
|
|
437
525
|
break;
|
|
438
526
|
}
|
|
527
|
+
foundProfilePath = profile;
|
|
439
528
|
if (profile) {
|
|
440
529
|
if (profile.endsWith(".yaml"))
|
|
441
530
|
console.log(`notice: ${profile} uses the legacy .yaml name for a JSON document; rename to architecture.json`);
|
|
442
531
|
try {
|
|
443
532
|
const parsed = JSON.parse(await readFile(profile, "utf8"));
|
|
444
|
-
if (parsed.schemaVersion !==
|
|
445
|
-
throw new Error("required identity fields are absent or profile is not
|
|
533
|
+
if (parsed.schemaVersion !== 4 || !parsed.project?.name || !parsed.project?.tier || !Array.isArray(parsed.capabilities))
|
|
534
|
+
throw new Error("required identity fields are absent or profile is not schema v4 (run profile-tool.mjs migrate for v3 profiles)");
|
|
446
535
|
console.log(`ok architecture profile: ${profile}`);
|
|
447
536
|
} catch (error) {
|
|
448
537
|
console.log(`invalid architecture profile: ${profile} (${error.message})`);
|
|
449
538
|
failed = true;
|
|
450
539
|
}
|
|
451
540
|
} else {
|
|
452
|
-
console.log(`missing architecture profile: ${profileCandidates[0]}`);
|
|
453
|
-
failed = true;
|
|
541
|
+
console.log(`missing architecture profile: ${profileCandidates[0]} (only needed for arch-guardian checks)`);
|
|
454
542
|
}
|
|
455
543
|
}
|
|
456
544
|
console.log(`ok runtime: Node ${process.versions.node}`);
|
|
457
545
|
const latest = await latestPublishedVersion();
|
|
458
|
-
if (latest && latest
|
|
459
|
-
console.log(`update available: installed ${packageVersion}, latest ${latest} — run: npx @vegastack/skills@latest add
|
|
546
|
+
if (latest && semverLess(packageVersion, latest))
|
|
547
|
+
console.log(`update available: installed ${packageVersion}, latest ${latest} — run: npx @vegastack/skills@latest add <skill> --force`);
|
|
548
|
+
else if (latest && semverLess(latest, packageVersion))
|
|
549
|
+
console.log(`ok installer version: ${packageVersion} (ahead of registry latest ${latest})`);
|
|
460
550
|
else if (latest)
|
|
461
551
|
console.log(`ok installer version: ${packageVersion} (latest)`);
|
|
462
552
|
else
|
|
463
553
|
console.log(`skipped installer version check (npmjs.org unreachable); installed ${packageVersion}`);
|
|
464
|
-
const { manifest } = await loadSource();
|
|
465
554
|
let installations = 0;
|
|
466
|
-
|
|
467
|
-
for (const
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
555
|
+
const checkScripts = [];
|
|
556
|
+
for (const skillName of await bundledSkills()) {
|
|
557
|
+
const { files } = await loadSource(skillName);
|
|
558
|
+
for (const agent of ["codex", "claude", "hermes"]) {
|
|
559
|
+
const destination = join(agent === "hermes" ? homedir() : base, surfaces[agent], skillName);
|
|
560
|
+
if (!await exists(destination))
|
|
561
|
+
continue;
|
|
562
|
+
installations += 1;
|
|
563
|
+
const result = await compare(destination, files);
|
|
564
|
+
console.log(`${result.status === "verified" ? "ok" : "invalid"} ${agent} ${skillName} installation${result.issues.length ? ` (${result.issues.join(", ")})` : ""}`);
|
|
565
|
+
if (result.status !== "verified")
|
|
566
|
+
failed = true;
|
|
567
|
+
const candidate = join(destination, "scripts", "validate-profile.mjs");
|
|
568
|
+
if (await exists(candidate))
|
|
569
|
+
checkScripts.push(candidate);
|
|
472
570
|
}
|
|
473
|
-
installations += 1;
|
|
474
|
-
const result = await compare(destination, manifest);
|
|
475
|
-
console.log(`${result.status === "verified" ? "ok" : "invalid"} ${agent} guardian installation${result.issues.length ? ` (${result.issues.join(", ")})` : ""}`);
|
|
476
|
-
if (result.status !== "verified")
|
|
477
|
-
failed = true;
|
|
478
|
-
if (!checkScript)
|
|
479
|
-
checkScript = join(destination, "scripts", "architecture-check.mjs");
|
|
480
571
|
}
|
|
481
|
-
if (!installations)
|
|
572
|
+
if (!installations) {
|
|
573
|
+
console.log("no bundled skills installed on any surface");
|
|
482
574
|
failed = true;
|
|
483
|
-
|
|
484
|
-
|
|
575
|
+
}
|
|
576
|
+
const firstCheckScript = checkScripts[0];
|
|
577
|
+
if (options.mode !== "global" && firstCheckScript && foundProfilePath && !failed) {
|
|
578
|
+
const result = spawnSync(process.execPath, [firstCheckScript, foundProfilePath], { encoding: "utf8" });
|
|
485
579
|
if (result.status === 0)
|
|
486
|
-
console.log("ok
|
|
580
|
+
console.log("ok profile validation");
|
|
487
581
|
else {
|
|
488
|
-
console.log(`invalid
|
|
582
|
+
console.log(`invalid profile: ${result.stdout.trim() || result.stderr.trim()}`);
|
|
489
583
|
failed = true;
|
|
490
584
|
}
|
|
491
585
|
}
|
|
@@ -498,6 +592,8 @@ async function main() {
|
|
|
498
592
|
return console.log(usage());
|
|
499
593
|
if (options.command === "version")
|
|
500
594
|
return console.log(packageVersion);
|
|
595
|
+
if (options.command === "list")
|
|
596
|
+
return list();
|
|
501
597
|
if (options.command === "add")
|
|
502
598
|
return install(options);
|
|
503
599
|
if (options.command === "verify")
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vegastack/skills",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Installer and verifier for VegaStack Agent Skills (Claude Code and
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Installer and verifier for VegaStack Agent Skills (Claude Code, Codex, and Hermes)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -16,13 +16,24 @@
|
|
|
16
16
|
"agent-skills",
|
|
17
17
|
"claude-code",
|
|
18
18
|
"codex",
|
|
19
|
+
"hermes",
|
|
19
20
|
"skill",
|
|
20
21
|
"architecture",
|
|
21
22
|
"vegastack"
|
|
22
23
|
],
|
|
23
|
-
"bin": {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
"bin": {
|
|
25
|
+
"vegastack-skills": "dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"skill",
|
|
30
|
+
"skill-integrity.json",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.11"
|
|
36
|
+
},
|
|
26
37
|
"scripts": {
|
|
27
38
|
"sync": "bun scripts/sync-skill.mjs",
|
|
28
39
|
"build": "bun run sync && bun build src/index.ts --target=node --outfile dist/index.js && chmod +x dist/index.js",
|